diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs
index 21af4134..e29fb637 100644
--- a/src/FastExpressionCompiler/FastExpressionCompiler.cs
+++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs
@@ -1,12044 +1,12356 @@
-//
-/*
-The MIT License (MIT)
-
-Copyright (c) 2016-2026 Maksim Volkau
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-*/
-
-// ReSharper disable CoVariantArrayConversion
-#nullable disable
-
-//#define LIGHT_EXPRESSION
-
-// todo: @wip disable for the published version!
-// #define TESTING // allows to test in Release in addition to the Debug
-
-#if DEBUG && NET6_0_OR_GREATER
-#define DEBUG_INFO_LOCAL_VARIABLE_USAGE
-#define DEMIT
-#define INTERPRETATION_DIAGNOSTICS
-#endif
-
-#if NET6_0_OR_GREATER
-#define SUPPORTS_IL_GENERATOR_REUSE
-#endif
-
-#if LIGHT_EXPRESSION
-#define SUPPORTS_ARGUMENT_PROVIDER
-#endif
-
-#if LIGHT_EXPRESSION
-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.SmallMap;
-#else
-namespace FastExpressionCompiler
-{
- using static System.Linq.Expressions.Expression;
- using PE = System.Linq.Expressions.ParameterExpression;
- using FastExpressionCompiler.ImTools;
- using FastExpressionCompiler.ILDecoder;
- using static FastExpressionCompiler.ImTools.SmallMap;
-#endif
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Reflection.Emit;
- using System.Threading;
- using System.Text;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using static System.Environment;
- using static CodePrinter;
-
- /// The flags for the compiler
- [Flags]
- public enum CompilerFlags : byte
- {
- /// The default flags: Invocation lambda is inlined, no debug info
- Default = 0,
- /// Prevents the inlining of the lambda in the Invocation expression to optimize for the multiple same lambda compiled once
- NoInvocationLambdaInlining = 1,
- /// Adds the Expression, ExpressionString, and CSharpString to the delegate closure for the debugging inspection
- EnableDelegateDebugInfo = 1 << 1,
- /// When the flag is set then instead of the returning `null` the specific exception is thrown*346
- ThrowOnNotSupportedExpression = 1 << 2,
- /// Will try to Interpret arithmetic, logical, comparison expressions for the primitive types,
- /// and emit the IL the result only instead of the whole computation.
- DisableInterpreter = 1 << 4
- }
-
- /// FEC Not Supported exception
- public sealed class NotSupportedExpressionException : InvalidOperationException
- {
- /// The reason
- public readonly ExpressionCompiler.Result Reason;
- /// Constructor
- public NotSupportedExpressionException(ExpressionCompiler.Result reason) : base(reason.ToString()) => Reason = reason;
- /// Constructor
- public NotSupportedExpressionException(ExpressionCompiler.Result reason, string message) : base(reason + ": " + message) => Reason = reason;
- }
-
- /// The interface is implemented by the compiled delegate Target if `CompilerFlags.EnableDelegateDebugInfo` is set.
- public interface IDelegateDebugInfo
- {
- /// The lambda expression object that was compiled to the delegate
- LambdaExpression Expression { get; }
-
- /// Delegate IL op-codes
- ILInstruction[] ILInstructions { get; }
-
- /// Enumerate any nested lambdas in the delegate
- [RequiresUnreferencedCode(Trimming.Message)]
- IEnumerable EnumerateNestedLambdas();
- }
-
- /// Compiles expression to delegate ~20 times faster than Expression.Compile.
- /// Partial to extend with your things when used as source file.
- // ReSharper disable once PartialTypeWithSinglePart
- [RequiresUnreferencedCode(Trimming.Message)]
- public static partial class ExpressionCompiler
- {
- #region Expression.CompileFast overloads for Delegate, Func, and Action
-
- /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static TDelegate CompileFast(this LambdaExpression lambdaExpr,
- bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) where TDelegate : class =>
- (TDelegate)(TryCompileBoundToFirstClosureParam(
- typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()));
-
- /// Compiles a static method to the passed IL Generator.
- /// Could be used as alternative for `CompileToMethod` like this .
- /// Check `IssueTests.Issue179_Add_something_like_LambdaExpression_CompileToMethod.cs` for example.
- public static bool CompileFastToIL(this LambdaExpression lambdaExpr, ILGenerator il, CompilerFlags flags = CompilerFlags.Default)
- {
- if ((flags & CompilerFlags.EnableDelegateDebugInfo) != 0)
- throw new NotSupportedException("The `CompilerFlags.EnableDelegateDebugInfo` is not supported because the debug info is gathered into the closure object which is not allowed for static lambda to be compiled to method.");
-
-#if LIGHT_EXPRESSION
- var paramExprs = lambdaExpr;
-#else
- var paramExprs = lambdaExpr.Parameters;
-#endif
- var bodyExpr = lambdaExpr.Body;
-
- var closureInfo = new ClosureInfo(ClosureStatus.ShouldBeStaticMethod);
- var nestedLambdas = new SmallList();
- if (!TryCollectBoundConstants(ref closureInfo, bodyExpr, paramExprs, null, ref nestedLambdas, flags))
- return false;
-
- if ((closureInfo.Status & ClosureStatus.HasClosure) != 0)
- return false;
-
- var parent = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (!EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent))
- return false;
-
- il.Demit(OpCodes.Ret);
- return true;
- }
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Delegate CompileFast(this LambdaExpression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Delegate)TryCompileBoundToFirstClosureParam(lambdaExpr.Type, lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Returns the System expression itself or convert the System expression into Light Expression
- public static Expression FromSysExpression(this System.Linq.Expressions.Expression lambdaExpr) where TDelegate : System.Delegate =>
-#if LIGHT_EXPRESSION
- lambdaExpr.ToLightExpression();
-#else
- lambdaExpr;
-#endif
-
- /// Returns the System expression itself or convert the System expression into Light Expression
- public static LambdaExpression FromSysExpression(this System.Linq.Expressions.LambdaExpression lambdaExpr) =>
-#if LIGHT_EXPRESSION
- lambdaExpr.ToLightExpression();
-#else
- lambdaExpr;
-#endif
-
- /// Unifies Compile for System.Linq.Expressions and FEC.LightExpression
- public static TDelegate CompileSys(this Expression lambdaExpr) where TDelegate : System.Delegate =>
- lambdaExpr
-#if LIGHT_EXPRESSION
- .ToLambdaExpression()
-#endif
- .Compile();
-
- /// Unifies Compile for System.Linq.Expressions and FEC.LightExpression
- public static Delegate CompileSys(this LambdaExpression lambdaExpr) =>
- lambdaExpr
-#if LIGHT_EXPRESSION
- .ToLambdaExpression()
-#endif
- .Compile();
-
- /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static TDelegate CompileFast(this Expression lambdaExpr, bool ifFastFailedReturnNull = false,
- CompilerFlags flags = CompilerFlags.Default) where TDelegate : System.Delegate =>
- ((LambdaExpression)lambdaExpr).CompileFast(ifFastFailedReturnNull, flags);
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(this Expression> lambdaExpr, bool ifFastFailedReturnNull = false,
- CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(this Expression> lambdaExpr,
- bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(this Expression> lambdaExpr,
- bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Func CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Func)TryCompileBoundToFirstClosureParam(typeof(Func), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(R), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(this Expression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(this Expression> lambdaExpr,
- bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(this Expression> lambdaExpr,
- bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(this Expression> lambdaExpr,
- bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing.
- public static Action CompileFast(
- this Expression> lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) =>
- (Action)TryCompileBoundToFirstClosureParam(typeof(Action), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- typeof(void), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- #endregion
-
- /// Tries to compile lambda expression to
- public static TDelegate TryCompile(this LambdaExpression lambdaExpr, CompilerFlags flags = CompilerFlags.Default)
- where TDelegate : class =>
- (TDelegate)TryCompileBoundToFirstClosureParam(typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- lambdaExpr.ReturnType, flags);
-
- /// Tries to compile lambda expression to
- /// with the provided closure object and constant expressions (or lack there of) -
- /// Constant expression should be the in order of Fields in closure object!
- /// Note 1: Use it on your own risk - FEC won't verify the expression is compile-able with passed closure, it is up to you!
- /// Note 2: The expression with NESTED LAMBDA IS NOT SUPPORTED!
- /// Note 3: `Label` and `GoTo` are not supported in this case, because they need first round to collect out-of-order labels
- public static TDelegate TryCompileWithPreCreatedClosure(this LambdaExpression lambdaExpr,
- params ConstantExpression[] closureConstantsExprs) where TDelegate : class =>
- lambdaExpr.TryCompileWithPreCreatedClosure(closureConstantsExprs, CompilerFlags.Default);
-
- /// Tries to compile lambda expression to
- /// with the provided closure object and constant expressions (or lack there of)
- public static TDelegate TryCompileWithPreCreatedClosure(this LambdaExpression lambdaExpr,
- ConstantExpression[] closureConstantsExprs, CompilerFlags flags)
- where TDelegate : class
- {
- var closureConstants = new object[closureConstantsExprs.Length];
- for (var i = 0; i < closureConstants.Length; i++)
- closureConstants[i] = closureConstantsExprs[i].Value;
-
- var closureInfo = new ClosureInfo(ClosureStatus.UserProvided | ClosureStatus.HasClosure, closureConstants);
- return TryCompileWithPreCreatedClosure(lambdaExpr, ref closureInfo, flags);
- }
-
- internal static TDelegate TryCompileWithPreCreatedClosure(
- this LambdaExpression lambdaExpr, ref ClosureInfo closureInfo, CompilerFlags flags) where TDelegate : class
- {
-#if LIGHT_EXPRESSION
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr);
-#else
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters);
-#endif
- var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes,
- typeof(ExpressionCompiler), skipVisibility: true);
-
- var il = method.GetILGenerator();
- EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo);
-
- var parent = lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (!EmittingVisitor.TryEmit(lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- il, ref closureInfo, flags, parent))
- return null;
-
- il.Demit(OpCodes.Ret);
-
- var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
- 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.
- public static TDelegate TryCompileWithoutClosure(this LambdaExpression lambdaExpr,
- CompilerFlags flags = CompilerFlags.Default) where TDelegate : class
- {
- var closureInfo = new ClosureInfo(ClosureStatus.UserProvided);
-#if LIGHT_EXPRESSION
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr);
-#else
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters);
-#endif
- var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, typeof(ArrayClosure),
- skipVisibility: true);
-
- var il = method.GetILGenerator();
- if (!EmittingVisitor.TryEmit(lambdaExpr.Body,
-#if LIGHT_EXPRESSION
- lambdaExpr,
-#else
- lambdaExpr.Parameters,
-#endif
- il, ref closureInfo, flags, lambdaExpr.ReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty))
- return null;
-
- il.Demit(OpCodes.Ret);
-
- var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
- var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure);
- FreePooledClosureTypeAndParamTypes(closurePlusParamTypes);
- return dlg;
- }
-
- 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 = 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);
-
- 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)
- {
- // 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 closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs);
-#endif
- // Try to avoid compilation altogether for Func delegates via Interpreter, see #468
- if (closureAndParamTypes.Length == 1 & returnType == typeof(bool)
- && bodyExpr.Type.IsPrimitive // todo: @feat #496 nullable is not supported yet
- && 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);
- var collectResult = TryCollectInfo(ref closureInfo, bodyExpr, paramExprs, null, ref closureInfo.NestedLambdas, flags);
- if (collectResult == Result.OK)
- {
- var constantsAndNestedLambdas = (closureInfo.Status & ClosureStatus.HasClosure) != 0
- ? closureInfo.GetArrayOfConstantsAndNestedLambdas()
- : null;
-
- ArrayClosure closure;
- if ((flags & CompilerFlags.EnableDelegateDebugInfo) == 0)
- closure = constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas);
- else
- {
- var debugLambdaExpr = Lambda(delegateType, bodyExpr, paramExprs?.ToReadOnlyList() ?? Tools.Empty());
- closure = new DebugArrayClosure(null, constantsAndNestedLambdas, debugLambdaExpr);
- }
-
- // 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);
-
- var il = DynamicMethodHacks.RentPooledOrNewILGenerator(dynMethod, returnType, closureAndParamTypes);
-
- 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))
- {
- il.Demit(OpCodes.Ret);
- compiledDelegate = dynMethod.CreateDelegate(delegateType, closure);
- if (closure is DebugArrayClosure diagClosure)
- diagClosure.ILInstructions = compiledDelegate.Method.ReadAllInstructions();
- }
-
- DynamicMethodHacks.FreePooledILGenerator(dynMethod, il);
- }
- FreePooledClosureTypeAndParamTypes(closureAndParamTypes);
-
- 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[] RentPooledOrNewClosureTypeToParamTypes(IParameterProvider paramExprs)
- {
- var count = paramExprs.ParameterCount;
-#else
- internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IReadOnlyList paramExprs)
- {
- var count = paramExprs.Count;
-#endif
- if (count == 0)
- return _closureAsASingleParamType;
-
- var pooledOrNew = count < 8 ? Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[count], null) ?? new Type[count + 1] : new Type[count + 1];
- pooledOrNew[0] = typeof(ArrayClosure);
- for (var i = 0; i < count; i++)
- {
- var paramExpr = paramExprs.GetParameter(i); // todo: @perf can we avoid calling virtual GetParameter() and maybe use intrinsic with NoByRef?
- pooledOrNew[i + 1] = !paramExpr.IsByRef ? paramExpr.Type : paramExpr.Type.MakeByRefType();
- }
-
- return pooledOrNew;
- }
-
- /// 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[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2)
- {
- var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[2], null) ?? new Type[3];
- pooledOrNew[0] = p0;
- pooledOrNew[1] = p1;
- pooledOrNew[2] = p2;
- 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)
- {
- 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 FreePooledClosureTypeAndParamTypes(Type[] closurePlusParamTypes)
- {
- var paramCountOnly = closurePlusParamTypes.Length - 1;
- if (paramCountOnly != 0 & paramCountOnly < 8)
- Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCountOnly], closurePlusParamTypes);
- }
-
- /// Freeing to the pool the array of the passed parameter types
- [MethodImpl((MethodImplOptions)256)]
- public static void FreePooledParamTypes(Type[] paramTypes)
- {
- var paramCount = paramTypes.Length;
- if (paramCount != 0 & paramCount < 8)
- Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCount - 1], paramTypes);
- }
-
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-
- /// Collects the lambda info for the compilation
- public sealed class NestedLambdaInfo
- {
- /// Compiled lambda
- public object Lambda; // todo: @perf can we use the NestedLambdaInfo itself instead of NestedLambdaWithConstantsAndNestedLambdas and avoid duplication in memory?
-
- /// The nested lambdas and their info
- public SmallList NestedLambdas;
-
- /// The lambda expression
- public LambdaExpression LambdaExpression;
-
- /// Parameters not passed through lambda parameter list But used inside lambda body.
- /// The top expression should Not contain not passed parameters.
- public SmallList NonPassedParameters;
-
- /// If the N's bit is set, it means the parameter is mutated (assigned or passed by-ref),
- /// where N is the index of the param in `NonPassedParameters`
- public ulong NonPassedParamMutatedIndexBits;
-
- /// Index of the compiled lambda in the parent lambda closure array
- public short LambdaVarIndex;
-
- /// Index of the variable which store the non-passed variables array before passing it to the closure constructor.
- /// It used to assign the closed variables from the outside of the nested lambda
- public short NonPassedParamsVarIndex;
-
- public NestedLambdaInfo(LambdaExpression lambdaExpression) => LambdaExpression = lambdaExpression;
-
- /// Compares 2 lambda expressions for equality
- public bool HasTheSameLambdaExpression(LambdaExpression lambda) => // todo: @unclear parameters or is comparing the body is enough?
- ReferenceEquals(LambdaExpression, lambda) ||
- ReferenceEquals(LambdaExpression.Body, lambda.Body)
-#if LIGHT_EXPRESSION
- && LambdaExpression.ParameterCount == lambda.ParameterCount
-#endif
- ;
-
- public override string ToString() =>
- $"Lambda: {(Lambda is NestedLambdaForNonPassedParams n ? "compiled+closure" : Lambda != null ? "compiled" : "null")}, Expr: {LambdaExpression.ToString()}";
- }
-
- [Flags]
- public enum ClosureStatus : byte
- {
- ToBeCollected = 1,
- UserProvided = 1 << 1,
- HasClosure = 1 << 2,
- ShouldBeStaticMethod = 1 << 3
- }
-
- [DebuggerDisplay("Target:{Target}, InlinedLambdaInvokeIndex:{InlinedLambdaInvokeIndex}, ReturnVariableIndexPlusOneAndIsDefined:{ReturnVariableIndexPlusOneAndIsDefined}")]
- public struct LabelInfo
- {
- public object Target;
- public short InlinedLambdaInvokeIndex;
- public short ReturnVariableIndexPlusOneAndIsDefined;
- public Label Label;
- public Label ReturnLabel;
- }
-
- /// Track the info required to build a closure object + some context information not directly related to closure.
- public struct ClosureInfo
- {
- /// Tracks that the last emit was an address
- public bool LastEmitIsAddress;
-
- // Tracks the current block nesting count in the stack of blocks in Collect and Emit phase
- private ushort _blockCount;
-
- /// Tracks the use of the variables in the blocks stack per variable,
- /// (uint) contains (ushort) BlockIndex in the upper bits and (ushort) VarIndex in the lower bits.
- /// to determine if variable is the local variable and in what block it's defined
- private SmallMap4, NoArrayPool>, RefEq> _varInBlock;
-
- /// The map of inlined invocations collected in TryCollect and then used in TryEmit
- internal SmallMap4> InlinedLambdaInvocation;
-
- /// New or Call expressions containing the complex expression, e.g. inlined Lambda Invoke or Try with Finally
- internal SmallMap4> ArgsContainingComplexExpression;
-
- internal bool HasComplexExpression;
-
- /// The stack for the lambda invocation and the labels bound to them
- internal SmallList, NoArrayPool> LambdaInvokeStackLabels;
-
- /// Tracks of how many gotos, labels referencing the specific target, they may be the same gotos expression,
- /// because the gotos may be reused multiple times in the big expression
- internal SmallMap4> TargetToGotosAndLabels;
-
- /// This is required because we have the return from the nested lambda expression,
- /// and when inlined in the parent lambda it is no longer the return but just a jump to the label.
- internal short CurrentInlinedLambdaInvokeIndex;
-
- public ClosureStatus Status;
-
- /// Constant expressions to find an index (by reference) of constant expression from compiled expression.
- public SmallList Constants;
-
- /// Constant usage count and variable index.
- /// It is a separate collection from the Constants because we directly convert later into the closure array
- public SmallList, NoArrayPool> ConstantUsageThenVarIndex;
-
- /// Parameters not passed through lambda parameter list But used inside lambda body.
- /// The top expression should Not contain not passed parameters.
- public SmallList NonPassedParameters;
-
- /// The nested lambdas and their info
- public SmallList NestedLambdas;
-
- /// Populates the info
- public ClosureInfo(ClosureStatus status)
- {
- Status = status;
- Constants = new SmallList();
- LastEmitIsAddress = false;
- CurrentInlinedLambdaInvokeIndex = -1;
- }
-
- /// Populates info directly with provided closure object and constants.
- public ClosureInfo(ClosureStatus status, object[] constValues)
- {
- Status = status;
-
- Constants = new SmallList(constValues ?? Tools.Empty());
- if (constValues != null)
- ConstantUsageThenVarIndex.InitCount(constValues.Length);
-
- LastEmitIsAddress = false;
- CurrentInlinedLambdaInvokeIndex = -1;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public bool ContainsConstantsOrNestedLambdas() => Constants.Count != 0 | NestedLambdas.Count != 0;
-
- public void AddConstantOrIncrementUsageCount(object value)
- {
- Status |= ClosureStatus.HasClosure;
- var constItems = Constants.Items;
- var constIndex = Constants.Count - 1;
- while (constIndex != -1 && !ReferenceEquals(constItems[constIndex], value))
- --constIndex;
- if (constIndex == -1)
- {
- Constants.Add(value);
- ConstantUsageThenVarIndex.Add(1);
- }
- else
- {
- ++ConstantUsageThenVarIndex.GetSurePresentRef(constIndex);
- }
- }
-
- [RequiresUnreferencedCode(Trimming.Message)]
- public void AddLabel(LabelTarget labelTarget, short inlinedLambdaInvokeIndex = -1)
- {
- // skip null labelTargets, e.g. it may be the case for LoopExpression.Continue
- if (labelTarget == null)
- return;
- GetLabelOrInvokeIndexByTarget(ref LambdaInvokeStackLabels, labelTarget, out var found);
- if (!found)
- {
- ref var label = ref LambdaInvokeStackLabels.AddDefaultAndGetRef();
- label.Target = labelTarget;
- label.InlinedLambdaInvokeIndex = inlinedLambdaInvokeIndex;
- }
- }
-
- public short AddInlinedLambdaInvoke(InvocationExpression e)
- {
- var count = LambdaInvokeStackLabels.Count;
- for (var i = 0; i < count; ++i)
- if (LambdaInvokeStackLabels.GetSurePresentRef(i).Target == e)
- return (short)i;
-
- ref var label = ref LambdaInvokeStackLabels.AddDefaultAndGetRef();
- label.Target = e;
- return (short)count;
- }
-
- public object[] GetArrayOfConstantsAndNestedLambdas()
- {
- var constCount = Constants.Count;
- var nestedLambdasCount = NestedLambdas.Count;
- if (constCount == 0)
- {
- if (nestedLambdasCount == 0)
- return null;
-
- var lambdaObjects = new object[nestedLambdasCount];
- for (var i = 0; i < lambdaObjects.Length; i++)
- lambdaObjects[i] = NestedLambdas.Items[i].Lambda;
- return lambdaObjects;
- }
-
- // if constants `count != 0`
- var constItems = Constants.Items;
- if (nestedLambdasCount == 0)
- return constItems;
-
- var constPlusLambdaCount = constCount + nestedLambdasCount;
-
- if (constItems.Length < constPlusLambdaCount)
- Array.Resize(ref constItems, constPlusLambdaCount);
-
- for (var i = 0; i < nestedLambdasCount; ++i)
- constItems[constCount + i] = NestedLambdas.Items[i].Lambda;
-
- return constItems;
- }
-
- /// Local variable index is not known in the collecting phase when we only need to decide if ParameterExpression is an actual parameter or variable
- [RequiresUnreferencedCode(Trimming.Message)]
- public void PushBlockWithVars(ParameterExpression blockVarExpr) =>
- PushVarInBlockMap(blockVarExpr, _blockCount++, 0);
-
- [RequiresUnreferencedCode(Trimming.Message)]
- public void PushBlockWithVars(ParameterExpression blockVarExpr, int varIndex) =>
- PushVarInBlockMap(blockVarExpr, _blockCount++, (ushort)varIndex);
-
- [RequiresUnreferencedCode(Trimming.Message)]
- public void PushBlockWithVars(IReadOnlyList blockVarExprs)
- {
- for (var i = 0; i < blockVarExprs.Count; i++)
- PushVarInBlockMap(blockVarExprs[i], _blockCount, 0);
- ++_blockCount;
- }
-
- [RequiresUnreferencedCode(Trimming.Message)]
- public void PushBlockAndConstructLocalVars(IReadOnlyList blockVarExprs, ILGenerator il)
- {
- for (var i = 0; i < blockVarExprs.Count; i++)
- {
- var varExpr = blockVarExprs[i];
- var varType = varExpr.Type;
- if (varExpr.IsByRef && !varType.IsByRef)
- varType = varType.MakeByRefType();
- PushVarInBlockMap(varExpr, _blockCount, (ushort)il.GetNextLocalVarIndex(varType));
- }
- ++_blockCount;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private void PushVarInBlockMap(ParameterExpression pe, ushort blockIndex, ushort varIndex)
- {
- ref var blocks = ref _varInBlock.Map.AddOrGetValueRef(pe, out _);
- if (blocks.Count == 0 || (blocks.GetLastSurePresentItem() >>> 16) != blockIndex)
- blocks.Add((uint)(blockIndex << 16) | varIndex);
- }
-
- public void PopBlock()
- {
- Debug.Assert(_blockCount > 0);
- var varCount = _varInBlock.Map.Count;
- for (var i = 0; i < varCount; ++i)
- {
- ref var varBlocks = ref _varInBlock.Map.GetSurePresentEntryRef(i);
- if (varBlocks.Value.Count == _blockCount)
- varBlocks.Value.RemoveLastSurePresentItem();
- }
- --_blockCount;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public bool IsLocalVar(ParameterExpression varParamExpr)
- {
- ref var blocks = ref _varInBlock.Map.TryGetValueRef(varParamExpr, out var found);
- return found && blocks.Count != 0;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public int GetDefinedLocalVarOrDefault(ParameterExpression varParamExpr)
- {
- ref var blocks = ref _varInBlock.Map.TryGetValueRef(varParamExpr, out var found);
- return found && blocks.Count != 0 // rare case with the block count 0 may occur when we collected the block and vars, but not yet defined the variable for it
- ? (int)(blocks.GetLastSurePresentItem() & ushort.MaxValue)
- : -1;
- }
- }
-
- internal static ref LabelInfo GetLabelOrInvokeIndexByTarget(ref this TLabels labels, object labelTarget, out bool found)
- where TLabels : struct, IIndexed
- {
- var count = labels.Count;
- for (var i = 0; i < count; ++i)
- {
- ref var label = ref labels.GetSurePresentRef(i);
- if (label.Target == labelTarget)
- {
- found = true;
- return ref label;
- }
- }
- found = false;
- return ref RefTools.GetNullRef();
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static Label GetOrDefineLabel(ref this LabelInfo label, ILGenerator il)
- {
- if ((label.ReturnVariableIndexPlusOneAndIsDefined & 1) == 0)
- {
- label.Label = il.DefineLabel();
- label.ReturnVariableIndexPlusOneAndIsDefined |= 1;
- }
- return label.Label;
- }
-
- public static readonly ArrayClosure EmptyArrayClosure = new ArrayClosure(null);
-
- public static FieldInfo ArrayClosureArrayField =
- typeof(ArrayClosure).GetField(nameof(ArrayClosure.ConstantsAndNestedLambdas));
-
- public static FieldInfo ArrayClosureWithNonPassedParamsField =
- typeof(ArrayClosureWithNonPassedParams).GetField(nameof(ArrayClosureWithNonPassedParams.NonPassedParams));
-
- private static ConstructorInfo[] _nonPassedParamsArrayClosureCtors = typeof(ArrayClosureWithNonPassedParams).GetConstructors();
-
- public static ConstructorInfo ArrayClosureWithNonPassedParamsAndConstantsCtor = _nonPassedParamsArrayClosureCtors[0];
-
- 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
- {
- // todo: @feature split into two to reduce copying
- public readonly object[] ConstantsAndNestedLambdas;
- public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas;
- }
-
- internal static IEnumerable EnumerateNestedLambdas(object[] constantsAndNestedLambdas)
- {
- if (constantsAndNestedLambdas != null && constantsAndNestedLambdas.Length != 0)
- // todo: @perf how to skip until the nested lambdas fast
- foreach (var item in constantsAndNestedLambdas)
- {
- if (item is IDelegateDebugInfo diagInfo)
- yield return diagInfo;
-
- var dlg = (item is NestedLambdaForNonPassedParams nestedLambda ? nestedLambda.NestedLambda : item) as Delegate;
- if (dlg != null && dlg.Target is IDelegateDebugInfo dlgDebugInfo)
- yield return dlgDebugInfo;
- }
- }
-
- /// 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
- {
- 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)
- {
- 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 class ArrayClosureWithNonPassedParams : ArrayClosure
- {
- public readonly object[] NonPassedParams;
- public ArrayClosureWithNonPassedParams(object[] nonPassedParams, object[] constantsAndNestedLambdas) : base(constantsAndNestedLambdas) =>
- NonPassedParams = nonPassedParams;
- public ArrayClosureWithNonPassedParams(object[] nonPassedParams) : base(null) =>
- NonPassedParams = nonPassedParams;
- }
-
- // todo: @perf this class is required until we (maybe) move to single constants list per lambda hierarchy
- // Those two classes are required only if there are non-passed parameters,
- // this class stores the context for creating the ArrayClosureWithNonPassedParams at the point of emitting the nested lambda.
- // See the #437 and #353 for the context
- public class NestedLambdaForNonPassedParams
- {
- public static FieldInfo NestedLambdaField =
- typeof(NestedLambdaForNonPassedParams).GetField(nameof(NestedLambda));
- public static FieldInfo NonPassedParamsField =
- typeof(NestedLambdaForNonPassedParams).GetField(nameof(NonPassedParams));
-
-#pragma warning disable CS0649
- public readonly object NestedLambda;
- public object[] NonPassedParams;
-#pragma warning restore CS0649
- public NestedLambdaForNonPassedParams(object nestedLambda) => NestedLambda = nestedLambda;
- }
-
- public class NestedLambdaForNonPassedParamsWithConstants : NestedLambdaForNonPassedParams
- {
- public static FieldInfo ConstantsAndNestedLambdasField =
- typeof(NestedLambdaForNonPassedParamsWithConstants).GetField(nameof(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();
-
- // todo: @mem @perf can we avoid closure creation over `f` and `c`?
- public static Func Curry(Func f, C c) =>
- () => f(c);
-
- public static Func Curry(Func f, C c) =>
- t1 => f(c, t1);
-
- public static Func Curry(Func f, C c) =>
- (t1, t2) => f(c, t1, t2);
-
- public static Func Curry(Func f, C c) =>
- (t1, t2, t3) => f(c, t1, t2, t3);
-
- public static Func Curry(Func f, C c) =>
- (t1, t2, t3, t4) => f(c, t1, t2, t3, t4);
-
- public static Func Curry(Func f,
- C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5);
-
- public static Func
- Curry(Func f, C c) =>
- (t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6);
-
- public static Func
- Curry(Func f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7) => f(c, t1, t2, t3, t4, t5, t6, t7);
-
- public static Func
- Curry(Func f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7, t8) => f(c, t1, t2, t3, t4, t5, t6, t7, t8);
-
- public static Func
- Curry(Func f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7, t8, t9) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9);
-
- public static Func
- Curry(Func f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10);
- }
-
- internal static class CurryClosureActions
- {
- public static readonly MethodInfo[] Methods = typeof(CurryClosureActions).GetMethods();
-
- public static Action Curry(Action a, C c) =>
- () => a(c);
-
- public static Action Curry(Action f, C c) =>
- t1 => f(c, t1);
-
- public static Action Curry(Action f, C c) =>
- (t1, t2) => f(c, t1, t2);
-
- public static Action Curry(Action f, C c) =>
- (t1, t2, t3) => f(c, t1, t2, t3);
-
- public static Action Curry(Action f, C c) =>
- (t1, t2, t3, t4) => f(c, t1, t2, t3, t4);
-
- public static Action Curry(Action f,
- C c) => (t1, t2, t3, t4, t5) => f(c, t1, t2, t3, t4, t5);
-
- public static Action
- Curry(Action f, C c) =>
- (t1, t2, t3, t4, t5, t6) => f(c, t1, t2, t3, t4, t5, t6);
-
- public static Action
- Curry(Action f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7) => f(c, t1, t2, t3, t4, t5, t6, t7);
-
- public static Action
- Curry(Action f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7, t8) => f(c, t1, t2, t3, t4, t5, t6, t7, t8);
-
- public static Action
- Curry(Action f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7, t8, t9) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9);
-
- public static Action
- Curry(Action f, C c) =>
- (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => f(c, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10);
- }
-
- #region Collect Bound Constants
-
- /// Helps to identify constants as the one to be put into the Closure
- public static bool IsClosureBoundConstant(object value, Type type) =>
- value is Delegate || type.IsArray ||
- !type.IsPrimitive && !type.IsEnum && value is string == false && value is Type == false && value is decimal == false;
-
- public enum Result
- {
- OK = 0,
- ExpressionIsNull = 1,
- ParameterIsNotVariableNorInPassedParameters = 2,
- NestedLambdaCompileError = 102,
- NotSupported_UnknownExpression = 1000,
- /// Multi-dimensional array initializer is not supported
- NotSupported_NewArrayInit_MultidimensionalArray = 1001,
- /// Quote is not supported
- NotSupported_Quote = 1002,
- /// Dynamic is not supported
- NotSupported_Dynamic = 1003,
- /// RuntimeVariables is not supported
- NotSupported_RuntimeVariables = 1004,
- /// MemberInit MemberBinding is not supported
- NotSupported_MemberInit_MemberBinding = 1005,
- /// MemberInit ListBinding is not supported
- NotSupported_MemberInit_ListBinding = 1006,
- /// Goto of the Return kind from the TryCatch is not supported
- NotSupported_Try_GotoReturnToTheFollowupLabel = 1007,
- /// Not supported assignment target
- NotSupported_Assign_Target = 1008,
- /// TypeEqual is not supported
- NotSupported_TypeEqual = 1009,
- /// `when` in catch is not supported yet
- 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)]
- public static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs, // `paramExprs` are required for nested lambda compilation
-#else
- IReadOnlyList paramExprs,
-#endif
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags)
- {
- var r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags);
- 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.
- /// Returns `OK` result if everything is fine and other result for error.
- public static Result TryCollectInfo(ref ClosureInfo closure, Expression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags)
- {
- var r = Result.OK;
- while (true)
- {
- if (expr == null)
- return Result.ExpressionIsNull;
-#if LIGHT_EXPRESSION
- if (expr.IsIntrinsic)
- return expr.TryCollectInfo(flags, ref closure, paramExprs, nestedLambda, ref rootNestedLambdas);
-#endif
- switch (expr.NodeType)
- {
- case ExpressionType.Constant:
-#if LIGHT_EXPRESSION
- if (expr == NullConstant | expr == FalseConstant | expr == TrueConstant || expr is IntConstantExpression)
- return r;
-#endif
- var constantExpr = (ConstantExpression)expr;
- var value = constantExpr.Value;
- if (value != null && IsClosureBoundConstant(value, value.GetType()))
- closure.AddConstantOrIncrementUsageCount(value);
- return Result.OK;
-
- case ExpressionType.Parameter:
- {
-#if LIGHT_EXPRESSION
- var paramCount = paramExprs.ParameterCount;
-#else
- var paramCount = paramExprs.Count;
-#endif
- // if parameter is used BUT is not in passed parameters and not in local variables,
- // it means parameter is provided by outer lambda and should be put in closure for current lambda
- var p = paramCount - 1;
- var parExpr = (PE)expr;
- while (p != -1 && !ReferenceEquals(paramExprs.GetParameter(p), parExpr)) --p;
- if (p == -1 && !closure.IsLocalVar(parExpr))
- {
- if (nestedLambda == null) // means that we are in the root lambda
- return Result.ParameterIsNotVariableNorInPassedParameters;
- closure.Status |= ClosureStatus.HasClosure;
- _ = nestedLambda.NonPassedParameters.GetIndexOrAdd(parExpr, default(RefEq));
- }
- return Result.OK;
- }
- case ExpressionType.Call:
- {
- var callExpr = (MethodCallExpression)expr;
- var callObjectExpr = callExpr.Object;
-
-#if SUPPORTS_ARGUMENT_PROVIDER
- var callArgs = (IArgumentProvider)callExpr;
-#else
- var callArgs = callExpr.Arguments;
-#endif
- var argCount = callArgs.GetCount();
- if (argCount == 0)
- {
- if (callObjectExpr != null)
- {
- expr = callObjectExpr;
- continue;
- }
- return Result.OK;
- }
-
- if (callObjectExpr != null &&
- (r = TryCollectInfo(ref closure, callObjectExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- var hasComplexExpression = false;
- for (var i = 0; i < argCount; i++)
- {
- closure.HasComplexExpression = false; // reset the flag because we want to know the real result after the arg collection
- if ((r = TryCollectInfo(ref closure, callArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- // if any argument is complex, then thw whole call should be complex,
- // because we cannot just store and restore a single argument, it should be done for all arguments
- hasComplexExpression |= closure.HasComplexExpression;
- }
-
- // propagate the value up the stack
- if (hasComplexExpression)
- {
- closure.HasComplexExpression = true;
- closure.ArgsContainingComplexExpression.Map.AddOrGetValueRef(callExpr, out _);
- }
- return r;
- }
-
- case ExpressionType.MemberAccess:
- var memberExpr = ((MemberExpression)expr).Expression;
- if (memberExpr == null)
- return r;
- expr = memberExpr;
- continue;
-
- case ExpressionType.New:
- {
- var newExpr = (NewExpression)expr;
-#if SUPPORTS_ARGUMENT_PROVIDER
- var ctorArgs = (IArgumentProvider)newExpr;
-#else
- var ctorArgs = newExpr.Arguments;
-#endif
- var argCount = ctorArgs.GetCount();
- if (argCount == 0)
- return r;
-
- var hasComplexExpression = false;
- for (var i = 0; i < argCount; i++)
- {
- closure.HasComplexExpression = false;
- if ((r = TryCollectInfo(ref closure, ctorArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- hasComplexExpression |= closure.HasComplexExpression;
- }
-
- // pop the value up the stack
- if (hasComplexExpression)
- {
- closure.HasComplexExpression = true;
- closure.ArgsContainingComplexExpression.Map.AddOrGetEntryRef(newExpr, out _);
- }
-
- return r;
- }
- case ExpressionType.NewArrayBounds:
- case ExpressionType.NewArrayInit:
- // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression
- if (expr.NodeType == ExpressionType.NewArrayInit && expr.Type.GetArrayRank() > 1)
- return Result.NotSupported_NewArrayInit_MultidimensionalArray;
-#if LIGHT_EXPRESSION
- var arrElems = (IArgumentProvider)expr;
- var elemCount = arrElems.ArgumentCount;
-#else
- var arrElems = ((NewArrayExpression)expr).Expressions;
- var elemCount = arrElems.Count;
-#endif
- if (elemCount == 0)
- return r;
- for (var i = 0; i < elemCount - 1; i++)
- if ((r = TryCollectInfo(ref closure, arrElems.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- expr = arrElems.GetArgument(elemCount - 1);
- continue;
-
- case ExpressionType.MemberInit:
- return TryCollectMemberInitExprConstants(
- ref closure, (MemberInitExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags);
-
- case ExpressionType.ListInit:
- return TryCollectListInitExprConstants(
- ref closure, (ListInitExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags);
-
- case ExpressionType.Lambda:
- // Here we look if the lambda is already stored in the nested lambdas tree (Collected+Compiled),
- // or if not found it Collects+Compiles the nested lambda here and adds to the nested lambda tree.
- var nestedLambdaExpr = (LambdaExpression)expr;
-
-#if LIGHT_EXPRESSION // todo: @simplify can we do better?
- var nestedParamExprs = (IParameterProvider)nestedLambdaExpr;
-#else
- var nestedParamExprs = nestedLambdaExpr.Parameters;
-#endif
- closure.Status |= ClosureStatus.HasClosure;
-
- // Look for the already collected lambdas starting from the root
- if (rootNestedLambdas.Count != 0 &&
- FindAlreadyCompiledNestedLambdaInfoInLambdas(ref rootNestedLambdas, nestedLambdaExpr, out var compiledNestedLambda))
- {
- if (nestedLambda != null)
- nestedLambda.NestedLambdas.Add(compiledNestedLambda);
- else
- rootNestedLambdas.Add(compiledNestedLambda);
-
- if (compiledNestedLambda.NonPassedParameters.Count != 0 &&
- !PropagateNonPassedParamsToOuterLambda(ref closure,
- nestedLambda, paramExprs, nestedParamExprs, ref compiledNestedLambda.NonPassedParameters))
- return Result.ParameterIsNotVariableNorInPassedParameters;
-
- return r;
- }
-
- var nestedClosure = new ClosureInfo(ClosureStatus.ToBeCollected);
- var newNestedLambda = new NestedLambdaInfo(nestedLambdaExpr);
-
- if (nestedLambda != null)
- nestedLambda.NestedLambdas.Add(newNestedLambda);
- else
- rootNestedLambdas.Add(newNestedLambda);
-
- if ((r = TryCollectInfo(ref nestedClosure, nestedLambdaExpr.Body, nestedParamExprs, newNestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- if (newNestedLambda.NonPassedParameters.Count != 0 &&
- !PropagateNonPassedParamsToOuterLambda(ref closure,
- nestedLambda, paramExprs, nestedParamExprs, ref newNestedLambda.NonPassedParameters))
- return Result.ParameterIsNotVariableNorInPassedParameters;
-
- if (!TryCompileNestedLambda(ref nestedClosure, newNestedLambda, flags))
- return Result.NestedLambdaCompileError;
-
- return r;
-
- case ExpressionType.Invoke:
- {
- var invokeExpr = (InvocationExpression)expr;
-#if SUPPORTS_ARGUMENT_PROVIDER
- var invokeArgs = (IArgumentProvider)invokeExpr;
-#else
- var invokeArgs = invokeExpr.Arguments;
-#endif
- var invokeArgCount = invokeArgs.GetCount();
- var invokedExpr = invokeExpr.Expression;
- if ((flags & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr)
- {
- var oldIndex = closure.CurrentInlinedLambdaInvokeIndex;
- closure.CurrentInlinedLambdaInvokeIndex = closure.AddInlinedLambdaInvoke(invokeExpr);
- closure.HasComplexExpression = false; // switch off because we have entered the inlined lambda
-
- ref var inlinedExpr = ref closure.InlinedLambdaInvocation.Map.AddOrGetValueRef(invokeExpr, out var found);
- if (!found)
- inlinedExpr = CreateInlinedLambdaInvocationExpression(invokeArgs, invokeArgCount, lambdaExpr);
-
- if ((r = TryCollectInfo(ref closure, inlinedExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- closure.HasComplexExpression = true;
- closure.CurrentInlinedLambdaInvokeIndex = oldIndex;
- return r;
- }
-
- // No inlining, collect the normal way
- if (invokeArgCount == 0)
- {
- expr = invokedExpr;
- continue;
- }
-
- if ((r = TryCollectInfo(ref closure, invokedExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- var lastArgIndex = invokeArgCount - 1;
- for (var i = 0; i < lastArgIndex; i++)
- if ((r = TryCollectInfo(ref closure, invokeArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- expr = invokeArgs.GetArgument(lastArgIndex);
- continue;
- }
- case ExpressionType.Conditional:
- var condExpr = (ConditionalExpression)expr;
- if ((r = TryCollectInfo(ref closure, condExpr.Test, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK ||
- (r = TryCollectInfo(ref closure, condExpr.IfFalse, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- expr = condExpr.IfTrue;
- continue;
-
- case ExpressionType.Block:
- var blockExpr = (BlockExpression)expr;
- var blockExprs = blockExpr.Expressions;
- var blockExprCount = blockExprs.Count;
- if (blockExprCount == 0)
- return r; // yeah, this is the real case - the block may not contain any expressions
-
- var varExprs = blockExpr.Variables;
- var varExprCount = varExprs?.Count ?? 0; // todo: @perf optimize for an empty and a single variable
-
- if (varExprCount == 1 & blockExprCount == 2 &&
- blockExprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign &&
- blockExprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign &&
- st0.Left == blockExprs[0] && st1.Right == blockExprs[0])
- {
- if ((r = TryCollectInfo(ref closure, st0.Right, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- expr = st1.Left;
- continue;
- }
-
- if (varExprCount == 1)
- closure.PushBlockWithVars(varExprs[0]);
- else if (varExprCount != 0)
- closure.PushBlockWithVars(varExprs);
-
- for (var i = 0; i < blockExprCount - 1; i++)
- if ((r = TryCollectInfo(ref closure, blockExprs[i], paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- expr = blockExprs[blockExprCount - 1];
- if (varExprCount == 0)
- continue; // in case of no variables we can collect the last expr without recursion
-
- if ((r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- closure.PopBlock();
- return r;
-
- case ExpressionType.Loop:
- var loopExpr = (LoopExpression)expr;
- closure.AddLabel(loopExpr.BreakLabel);
- closure.AddLabel(loopExpr.ContinueLabel);
- expr = loopExpr.Body;
- continue;
-
- case ExpressionType.Index:
- var indexExpr = (IndexExpression)expr;
-#if SUPPORTS_ARGUMENT_PROVIDER
- var indexArgs = (IArgumentProvider)indexExpr;
-#else
- var indexArgs = indexExpr.Arguments;
-#endif
- var indexArgCount = indexArgs.GetCount();
- for (var i = 0; i < indexArgCount; i++)
- if ((r = TryCollectInfo(ref closure, indexArgs.GetArgument(i), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- if (indexExpr.Object == null)
- return r;
- expr = indexExpr.Object;
- continue;
-
- case ExpressionType.Try:
- {
- closure.HasComplexExpression = false;
- r = TryCollectTryExprInfo(ref closure, (TryExpression)expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags);
- closure.HasComplexExpression = true;
- return r;
- }
- case ExpressionType.Label:
- var labelExpr = (LabelExpression)expr;
- closure.AddLabel(labelExpr.Target, closure.CurrentInlinedLambdaInvokeIndex);
- if (labelExpr.Target != null)
- closure.TargetToGotosAndLabels.Map.AddOrGetValueRef(labelExpr.Target, out _).Item2++;
- if (labelExpr.DefaultValue == null)
- return r;
- expr = labelExpr.DefaultValue;
- continue;
-
- case ExpressionType.Goto:
- var gotoExpr = (GotoExpression)expr;
- if (gotoExpr.Target != null)
- closure.TargetToGotosAndLabels.Map.AddOrGetValueRef(gotoExpr.Target, out _).Item1++;
- if (gotoExpr.Value == null)
- return r;
- expr = gotoExpr.Value;
- continue;
-
- case ExpressionType.Switch:
- var switchExpr = ((SwitchExpression)expr);
- if ((r = TryCollectInfo(ref closure, switchExpr.SwitchValue, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK ||
- switchExpr.DefaultBody != null && // todo: @check is the order of collection affects the result?
- (r = TryCollectInfo(ref closure, switchExpr.DefaultBody, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- var switchCases = switchExpr.Cases;
- if (switchCases.Count != 0)
- {
- for (var i = 0; i < switchCases.Count - 1; i++)
- if ((r = TryCollectInfo(ref closure, switchCases[i].Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- expr = switchCases[switchCases.Count - 1].Body;
- continue;
- }
- return r;
-
- case ExpressionType.Extension:
- expr = expr.Reduce();
- continue;
-
- case ExpressionType.Default:
- return r;
-
- case ExpressionType.TypeIs:
- case ExpressionType.TypeEqual:
- expr = ((TypeBinaryExpression)expr).Expression;
- continue;
-
- case ExpressionType.Quote: // todo: @feature - is not supported yet
- return Result.NotSupported_Quote;
- case ExpressionType.Dynamic: // todo: @feature - is not supported yet
- return Result.NotSupported_Dynamic;
- case ExpressionType.RuntimeVariables: // todo: @feature - is not supported yet
- return NotSupported_RuntimeVariables;
-
- case ExpressionType.DebugInfo: // todo: @feature - is not supported yet
- return r; // todo: @unclear - just ignoring the info for now
-
- default:
- if (expr is UnaryExpression unaryExpr)
- {
- if (unaryExpr.Operand is null)
- return r;
- expr = unaryExpr.Operand;
- continue;
- }
-
- if (expr is BinaryExpression binaryExpr)
- {
- if ((r = TryCollectInfo(ref closure, binaryExpr.Left, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- expr = binaryExpr.Right;
- continue;
- }
-
- return Result.NotSupported_UnknownExpression;
- }
- }
- }
-
- private static Expression CreateInlinedLambdaInvocationExpression(
-#if SUPPORTS_ARGUMENT_PROVIDER
- IArgumentProvider invokeArgs,
-#else
- IReadOnlyList invokeArgs,
-#endif
- int invokeArgCount, LambdaExpression lambdaExpr)
- {
- // Check the actual lambda return type in case it differs from the Body type,
- // e.g. often case for the Action lambdas where the Body type is ignored in favor of `void`.
- var lambdaReturnType = lambdaExpr.ReturnType;
- var lambdaBodyExpr = lambdaExpr.Body;
- if (invokeArgCount == 0)
- return lambdaReturnType == lambdaBodyExpr.Type ? lambdaBodyExpr :
- lambdaReturnType == typeof(void) ? Block(typeof(void), lambdaBodyExpr) :
- Convert(lambdaBodyExpr, lambdaReturnType);
-
- // To inline the lambda we will wrap its body into a block, parameters into the block variables,
- // and the invocation arguments into the variable assignments, see #278.
-#if LIGHT_EXPRESSION
- var lambdaPars = (IParameterProvider)lambdaExpr;
-#else
- var lambdaPars = lambdaExpr.Parameters;
-#endif
- SmallList, NoArrayPool> inlinedBlockExprs = default;
- SmallList, NoArrayPool> savedVars = default;
- SmallList, NoArrayPool> savedVarsBlockExprs = default;
-
- for (var i = 0; i < invokeArgCount; i++)
- {
- var lambdaPar = lambdaPars.GetParameter(i);
- var invokeArg = invokeArgs.GetArgument(i);
-
- // The case of reusing the parameters or variables in the different lambdas,
- // see the test `NestedLambdaTests.Hmm_I_can_use_the_same_parameter_for_outer_and_nested_lambda`
- // and the `Issue401_What_happens_if_inlined_invocation_of_lambda_overrides_the_same_parameter`.
- if (lambdaPar == invokeArg)
- {
- var savedPar = Parameter(lambdaPar.Type, lambdaPar.Name + "_" + lambdaPar.GetHashCode().ToString());
- savedVars.Add(savedPar);
- savedVarsBlockExprs.Add(Assign(savedPar, invokeArg));
- inlinedBlockExprs.Add(Assign(lambdaPar, savedPar));
- continue;
- }
-
- inlinedBlockExprs.Add(Assign(lambdaPar, invokeArg));
- }
-
- inlinedBlockExprs.Add(lambdaBodyExpr);
-
-#if LIGHT_EXPRESSION
- var inlinedBlock = lambdaReturnType == lambdaBodyExpr.Type
- ? Block(lambdaPars.ToReadOnlyList(), in inlinedBlockExprs)
- : Block(lambdaReturnType, lambdaPars.ToReadOnlyList(), in inlinedBlockExprs);
- if (savedVars.Count != 0)
- {
- savedVarsBlockExprs.Add(inlinedBlock);
- inlinedBlock = Block(savedVars.ToArray(), in savedVarsBlockExprs);
- }
-#else
- var inlinedBlock = lambdaReturnType == lambdaBodyExpr.Type
- ? Block(lambdaPars, inlinedBlockExprs.ToArray())
- : Block(lambdaReturnType, lambdaPars, inlinedBlockExprs.ToArray());
- if (savedVars.Count != 0)
- {
- savedVarsBlockExprs.Add(inlinedBlock);
- inlinedBlock = Block(savedVars.ToArray(), savedVarsBlockExprs.ToArray());
- }
-#endif
- return inlinedBlock;
- }
-
-#if LIGHT_EXPRESSION
- private static bool PropagateNonPassedParamsToOuterLambda(ref ClosureInfo closure, NestedLambdaInfo lambda,
- IParameterProvider paramExprs, IParameterProvider nestedLambdaParamExprs, ref SmallList nestedNonPassedParams)
- {
- var paramExprCount = paramExprs.ParameterCount;
- var nestedLambdaParamExprCount = nestedLambdaParamExprs.ParameterCount;
-#else
- private static bool PropagateNonPassedParamsToOuterLambda(ref ClosureInfo closure, NestedLambdaInfo lambda,
- IReadOnlyList paramExprs, IReadOnlyList nestedLambdaParamExprs, ref SmallList nestedNonPassedParams)
- {
- var paramExprCount = paramExprs.Count;
- var nestedLambdaParamExprCount = nestedLambdaParamExprs.Count;
-#endif
- // If nested non passed parameter is not matched with any outer passed parameter,
- // then we ensure it goes to the outer non passed parameter.
- // But having the non-passed parameter in the root expression (nestedLambda == null) is invalid, and results in false.
- for (var i = 0; i < nestedNonPassedParams.Count; i++)
- {
- var nestedNonPassedParam = nestedNonPassedParams.GetSurePresentRef(i);
-
- var isInNestedLambda = false;
- if (nestedLambdaParamExprCount != 0)
- for (var p = 0; !isInNestedLambda && p < nestedLambdaParamExprCount; ++p)
- isInNestedLambda = ReferenceEquals(nestedLambdaParamExprs.GetParameter(p), nestedNonPassedParam);
-
- var isInLambda = false;
- if (paramExprCount != 0)
- for (var p = 0; !isInLambda && p < paramExprCount; ++p)
- isInLambda = ReferenceEquals(paramExprs.GetParameter(p), nestedNonPassedParam);
-
- if (!isInNestedLambda & !isInLambda)
- {
- if (closure.IsLocalVar(nestedNonPassedParam))
- continue;
- if (lambda == null) // means that we at the root level lambda, and non-passed parameter cannot be provided
- return false;
- _ = lambda.NonPassedParameters.GetIndexOrAdd(nestedNonPassedParam, default(RefEq));
- }
- }
-
- return true;
- }
-
- private static bool FindAlreadyCompiledNestedLambdaInfoInLambdas(
- ref SmallList nestedLambdas, LambdaExpression lambdaExpr, out NestedLambdaInfo found)
- {
- var nestedLambdasCount = nestedLambdas.Count;
- for (var i = 0; i < nestedLambdasCount; ++i)
- {
- var nestedLambda = nestedLambdas.Items[i];
- if (nestedLambda.HasTheSameLambdaExpression(lambdaExpr))
- {
- found = nestedLambda;
- return true;
- }
-
- if (nestedLambda.NestedLambdas.Count != 0)
- return FindAlreadyCompiledNestedLambdaInfoInLambdas(ref nestedLambda.NestedLambdas, lambdaExpr, out found);
- }
-
- found = null;
- return false;
- }
-
- 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
- // 3. Add the compiled lambda to closure of outer lambda for later invocation
- var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression;
- var nestedReturnType = nestedLambdaExpr.ReturnType;
- var nestedLambdaBody = nestedLambdaExpr.Body;
-#if LIGHT_EXPRESSION
- var nestedLambdaParamExprs = (IParameterProvider)nestedLambdaExpr;
-
- if (nestedLambdaBody is NoArgsNewClassIntrinsicExpression newExpr)
- {
- var paramTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs);
- nestedLambdaInfo.Lambda = CompileNoArgsNew(newExpr, nestedLambdaExpr.Type, paramTypes, nestedReturnType, flags);
- FreePooledClosureTypeAndParamTypes(paramTypes);
- return true;
- }
-#else
- var nestedLambdaParamExprs = nestedLambdaExpr.Parameters;
-#endif
- // copy the nested lambdas and non-passed parameters to closure info to read them in TryEmit
- nestedClosureInfo.NestedLambdas = nestedLambdaInfo.NestedLambdas;
- nestedClosureInfo.NonPassedParameters = nestedLambdaInfo.NonPassedParameters;
-
- 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 = !hasDebugInfo
- ? (constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas))
- : new DebugArrayClosure(null, constantsAndNestedLambdas, nestedLambdaExpr);
-
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs);
-
- var method = new DynamicMethod(string.Empty, nestedReturnType, closurePlusParamTypes, typeof(ArrayClosure), true);
- var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, nestedReturnType, closurePlusParamTypes);
-
- if (constantsAndNestedLambdas != null)
- EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref nestedClosureInfo);
-
- var parent = nestedReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (nestedReturnType.IsByRef)
- parent |= ParentFlags.ReturnByRef;
-
- 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);
-
- nestedLambdaInfo.Lambda = !hasNonPassedParameters
- ? nestedLambda
- : !hasDebugInfo
- ? constantsAndNestedLambdas == null
- ? new NestedLambdaForNonPassedParams(nestedLambda)
- : new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, constantsAndNestedLambdas)
- : new NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(nestedLambda, constantsAndNestedLambdas, nestedLambdaExpr);
-
- 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 TDelegate d)
- where TDelegate : Delegate => d?.Target as IDelegateDebugInfo;
-
-#if LIGHT_EXPRESSION
- private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr,
- IParameterProvider paramExprs, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags)
- {
- var newExpr = expr.Expression;
- var binds = (IArgumentProvider)expr;
- var count = binds.ArgumentCount;
-#else
- private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr,
- IReadOnlyList paramExprs, NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags)
- {
- var newExpr = expr.NewExpression;
- var binds = expr.Bindings;
- var count = binds.Count;
-#endif
- var r = Result.OK;
- if ((r = TryCollectInfo(ref closure, newExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- for (var i = 0; i < count; ++i)
- {
- var b = binds.GetArgument(i);
- if (b.BindingType != MemberBindingType.Assignment)
- return b.BindingType == MemberBindingType.MemberBinding ? Result.NotSupported_MemberInit_MemberBinding : Result.NotSupported_MemberInit_ListBinding; // todo: @feature MemberMemberBinding and the MemberListBinding is not supported yet.
-
- if ((r = TryCollectInfo(ref closure, ((MemberAssignment)b).Expression, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- }
- return r;
- }
-
- private static Result TryCollectListInitExprConstants(ref ClosureInfo closure, ListInitExpression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags)
- {
- var newExpr = expr.NewExpression;
- var inits = expr.Initializers;
- var count = inits.Count;
-
- var r = Result.OK;
- if ((r = TryCollectInfo(ref closure, newExpr, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- for (var i = 0; i < count; ++i)
- {
- var elemInit = inits.GetArgument(i);
- var args = elemInit.Arguments;
- var argCount = args.Count;
- for (var a = 0; a < argCount; ++a)
- if ((r = TryCollectInfo(ref closure, args.GetArgument(a), paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- }
- return r;
- }
-
- private static Result TryCollectTryExprInfo(ref ClosureInfo closure, TryExpression tryExpr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags)
- {
- var r = Result.OK;
- if ((r = TryCollectInfo(ref closure, tryExpr.Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- var catchBlocks = tryExpr.Handlers;
- for (var i = 0; i < catchBlocks.Count; i++)
- {
- var catchBlock = catchBlocks[i];
- var catchExVar = catchBlock.Variable;
- if (catchExVar != null)
- {
- closure.PushBlockWithVars(catchExVar);
- if ((r = TryCollectInfo(ref closure, catchExVar, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
- }
-
- if (catchBlock.Filter != null &&
- (r = TryCollectInfo(ref closure, catchBlock.Filter, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- if ((r = TryCollectInfo(ref closure, catchBlock.Body, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- if (catchExVar != null)
- closure.PopBlock();
- }
-
- var faultOrFinally = tryExpr.Fault ?? tryExpr.Finally;
- if (faultOrFinally != null &&
- (r = TryCollectInfo(ref closure, faultOrFinally, paramExprs, nestedLambda, ref rootNestedLambdas, flags)) != Result.OK)
- return r;
-
- return r;
- }
-
- #endregion
-
- /// The minimal context-aware flags set by parent
- [Flags]
- public enum ParentFlags
- {
- /// Default is no flags
- Empty = 0,
- /// The result of expression is ignored and maybe popped out
- IgnoreResult = 1 << 1,
- /// Some parent is the call expression
- Call = 1 << 2,
- /// Any Parent Expression is a MemberExpression
- MemberAccess = 1 << 3,
- /// Some arithmetic operation
- Arithmetic = 1 << 4,
- /// Subject
- Coalesce = 1 << 5,
- /// Expression with instance object (method call or member access or array access)
- InstanceAccess = 1 << 6,
- /// Subject
- DupIt = 1 << 7,
- /// Subject
- TryCatch = 1 << 8,
- /// Combination`of InstanceAccess and Call
- InstanceCall = Call | InstanceAccess,
- /// Constructor
- Ctor = 1 << 9,
- /// Constructor call
- CtorCall = Call | Ctor,
- /// Indexer
- IndexAccess = 1 << 10,
- /// Invoking the inlined lambda (the default System.Expression behavior)
- InlinedLambdaInvoke = 1 << 11,
- /// Indicate if the part AT LEAST participates in the assignment on the left side,
- /// it may also participate in the right side, e.g. ++x.Bar
- AssignmentLeftValue = 1 << 12,
- /// Indicates the ONLY right value of assignment, e.g. `p` in `foo.Bar += p`
- AssignmentRightValue = 1 << 13,
- /// Assigning the ref of the right value to the left, e.g. in `var a = ref b[1]` we are passing this flag for the `ref b[1]`
- AssignmentByRef = 1 << 14,
- /// Indicates the root lambda call
- LambdaCall = 1 << 15,
- /// ReturnByRef
- ReturnByRef = 1 << 16,
- /// The block result
- BlockResult = 1 << 17,
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public static bool IgnoresResult(this ParentFlags parent) => (parent & ParentFlags.IgnoreResult) != 0;
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool EmitPopIfIgnoreResult(this ILGenerator il, ParentFlags parent)
- {
- if ((parent & ParentFlags.IgnoreResult) != 0)
- il.Demit(OpCodes.Pop);
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool TryEmitBoxOf(this ILGenerator il, Type sourceType)
- {
- if (sourceType.IsValueType)
- il.Demit(OpCodes.Box, sourceType);
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool TryEmitUnboxOf(this ILGenerator il, Type sourceType)
- {
- if (sourceType.IsValueType)
- il.Demit(OpCodes.Unbox_Any, sourceType);
- return true;
- }
-
- /// Supports emitting of selected expressions, e.g. lambdaExpr are not supported yet.
- /// When emitter find not supported expression it will return false from , so I could fallback
- /// to normal and slow Expression.Compile.
- [RequiresUnreferencedCode(Trimming.Message)]
- public static class EmittingVisitor
- {
- // todo: @perf use UnsafeAccessAttribute
- /// Get a type from handle
- public static readonly MethodInfo GetTypeFromHandleMethod =
- ((Func)Type.GetTypeFromHandle).Method;
- private static readonly MethodInfo _objectEqualsMethod =
- ((Func)object.Equals).Method;
-
- public static bool TryEmit(Expression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1)
- {
- var exprType = expr.Type;
- while (true)
- {
- closure.LastEmitIsAddress = false;
-#if LIGHT_EXPRESSION
- if (expr.IsIntrinsic)
- return expr.TryEmit(setup, ref closure, paramExprs, il, parent, byRefIndex);
-#endif
- var nodeType = expr.NodeType;
- switch (nodeType)
- {
- case ExpressionType.Parameter:
- return (parent & ParentFlags.IgnoreResult) != 0 ||
- TryEmitParameter((ParameterExpression)expr, paramExprs, il, ref closure, setup, parent, byRefIndex);
-
- case ExpressionType.TypeAs:
- case ExpressionType.IsTrue:
- case ExpressionType.IsFalse:
- case ExpressionType.Increment:
- case ExpressionType.Decrement:
- case ExpressionType.Negate:
- case ExpressionType.NegateChecked:
- case ExpressionType.OnesComplement:
- case ExpressionType.UnaryPlus:
- case ExpressionType.Unbox:
- return TryEmitSimpleUnaryExpression((UnaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.TypeIs:
- case ExpressionType.TypeEqual:
- return TryEmitTypeIsOrEqual((TypeBinaryExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Convert:
- case ExpressionType.ConvertChecked:
- return TryEmitConvert((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.ArrayIndex:
- var arrIndexExpr = (BinaryExpression)expr;
- return TryEmit(arrIndexExpr.Left, paramExprs, il, ref closure, setup, parent | ParentFlags.IndexAccess)
- && TryEmit(arrIndexExpr.Right, paramExprs, il, ref closure, setup, parent | ParentFlags.IndexAccess) // #265
- && TryEmitArrayIndexGet(il, exprType, ref closure, parent);
-
- case ExpressionType.ArrayLength:
- if (!TryEmit(((UnaryExpression)expr).Operand, paramExprs, il, ref closure, setup, parent))
- return false;
- if ((parent & ParentFlags.IgnoreResult) == 0)
- il.Demit(OpCodes.Ldlen);
- return true;
-
- case ExpressionType.Constant:
- return (parent & ParentFlags.IgnoreResult) != 0 ||
- TryEmitConstant((ConstantExpression)expr, exprType, il, ref closure, byRefIndex);
-
- case ExpressionType.Call:
- return TryEmitMethodCall(expr, paramExprs, il, ref closure, setup, parent, byRefIndex);
-
- case ExpressionType.MemberAccess:
- return TryEmitMemberGet((MemberExpression)expr, paramExprs, il, ref closure, setup, parent, byRefIndex);
-
- case ExpressionType.New:
- return TryEmitNew(expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.NewArrayBounds:
- return EmitNewArrayBounds((NewArrayExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.NewArrayInit:
- return EmitNewArrayInit((NewArrayExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.MemberInit:
- return EmitMemberInit((MemberInitExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.ListInit:
- return TryEmitListInit((ListInitExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Lambda:
- return TryEmitNestedLambda((LambdaExpression)expr, paramExprs, il, ref closure);
-
- case ExpressionType.Invoke:
- return TryEmitInvoke((InvocationExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.GreaterThan:
- case ExpressionType.GreaterThanOrEqual:
- case ExpressionType.LessThan:
- case ExpressionType.LessThanOrEqual:
- case ExpressionType.Equal:
- case ExpressionType.NotEqual:
- {
- if (exprType.IsPrimitive && Interpreter.TryInterpretBool(out var boolResult, expr, setup))
- {
- if ((parent & ParentFlags.IgnoreResult) == 0)
- il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- return true;
- }
- return TryEmitComparison(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, exprType, nodeType, paramExprs, il,
- ref closure, setup, parent);
- }
- case ExpressionType.Add:
- case ExpressionType.Subtract:
- case ExpressionType.Multiply:
- case ExpressionType.Divide:
- case ExpressionType.Modulo:
- case ExpressionType.And:
- case ExpressionType.Or:
- case ExpressionType.ExclusiveOr:
- case ExpressionType.LeftShift:
- case ExpressionType.RightShift:
- {
- return exprType.IsPrimitive
- && TryInterpretAndEmitResult(expr, il, parent, setup)
- || TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il,
- ref closure, setup, parent);
- }
- // todo: @feature #472 add interpretation when those node types are supported
- case ExpressionType.AddChecked:
- case ExpressionType.SubtractChecked:
- case ExpressionType.MultiplyChecked:
- case ExpressionType.Power:
- return TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il,
- ref closure, setup, parent);
-
- case ExpressionType.AndAlso:
- case ExpressionType.OrElse:
- {
- if (!exprType.IsPrimitive) // todo: @feat #496
- {
- Debug.WriteLine("Unsupported: Nullable in || or && (is invalid C# but valid expression) is not supported yet, see #480: " + expr);
- return false;
- }
- if (Interpreter.TryInterpretBool(out var resultBool, expr, setup))
- {
- if ((parent & ParentFlags.IgnoreResult) == 0)
- il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- return true;
- }
- return TryEmitLogicalOperator((BinaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent);
- }
- case ExpressionType.Not:
- {
- if (!exprType.IsPrimitive) // todo: @feat #496
- {
- Debug.WriteLine("Unsupported: Nullable in !x (is invalid C# but valid expression) is not supported yet, see #480: " + expr);
- return false;
- }
-
- if (Interpreter.TryInterpretBool(out var resultBool, expr, setup))
- {
- if ((parent & ParentFlags.IgnoreResult) == 0)
- il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- return true;
- }
- return TryEmitNot((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent);
- }
- case ExpressionType.Coalesce:
- return TryEmitCoalesceOperator((BinaryExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Conditional:
- var condExpr = (ConditionalExpression)expr;
- var testExpr = condExpr.Test;
- if (Interpreter.TryInterpretBool(out var testIsTrue, testExpr, setup))
- {
- expr = testIsTrue ? condExpr.IfTrue : condExpr.IfFalse;
- continue; // no recursion, just continue with the left or right side of condition
- }
- return TryEmitConditional(testExpr, condExpr.IfTrue, condExpr.IfFalse, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.PostIncrementAssign:
- case ExpressionType.PreIncrementAssign:
- return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Add,
- nodeType == ExpressionType.PostIncrementAssign, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.PostDecrementAssign:
- case ExpressionType.PreDecrementAssign:
- return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Subtract,
- nodeType == ExpressionType.PostDecrementAssign, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.AddAssign:
- case ExpressionType.AddAssignChecked:
- case ExpressionType.SubtractAssign:
- case ExpressionType.SubtractAssignChecked:
- case ExpressionType.MultiplyAssign:
- case ExpressionType.MultiplyAssignChecked:
- case ExpressionType.DivideAssign:
- case ExpressionType.ModuloAssign:
- case ExpressionType.PowerAssign:
- case ExpressionType.AndAssign:
- case ExpressionType.OrAssign:
- case ExpressionType.ExclusiveOrAssign:
- case ExpressionType.LeftShiftAssign:
- case ExpressionType.RightShiftAssign:
- case ExpressionType.Assign:
- var ba = (BinaryExpression)expr;
- return TryEmitArithmeticAndOrAssign(ba.Left, ba.Right, exprType,
- AssignToArithmeticOrSelf(nodeType), false, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Block:
- {
- var blockExpr = (BlockExpression)expr;
- var blockVarExprs = blockExpr.Variables;
- var blockVarCount = blockVarExprs?.Count ?? 0;
- var statementExprs = blockExpr.Expressions; // Trim the expressions after the Throw - #196
- var statementCount = statementExprs.Count;
- if (statementCount == 0)
- return true; // yeah, it is a valid thing
-
- if (blockVarCount == 1 & statementCount == 2 &&
- statementExprs[0] is BinaryExpression st0 && st0.NodeType == ExpressionType.Assign &&
- statementExprs[1] is BinaryExpression st1 && st1.NodeType == ExpressionType.Assign &&
- st0.Left == blockVarExprs[0] && st1.Right == blockVarExprs[0])
- return TryEmitArithmeticAndOrAssign(st1.Left, st0.Right, st0.Left.Type,
- ExpressionType.Assign, false, paramExprs, il, ref closure, setup, parent);
-
- if (blockVarCount != 0)
- closure.PushBlockAndConstructLocalVars(blockVarExprs, il);
-
- expr = statementExprs[statementCount - 1]; // The last (result) statement in block will provide the result
-
- // Try to trim the statements from the end of the Block up to the Throw (if any),
- // or to the prior Label as in the #442 case 2
- if (statementCount > 1)
- {
- var throwIndex = statementCount - 1;
- for (; throwIndex != -1; --throwIndex)
- {
- var se = statementExprs[throwIndex];
- if (se.NodeType == ExpressionType.Label)
- {
- throwIndex = -1; // stop the search
- break;
- }
- if (se.NodeType == ExpressionType.Throw)
- break;
- }
-
- // If we have a Throw and it is not the last one
- if (throwIndex != -1 && throwIndex != statementCount - 1)
- {
- // Change the Throw return type to match the one for the Block, and adjust the statement count
- expr = Expression.Throw(((UnaryExpression)statementExprs[throwIndex]).Operand, blockExpr.Type);
- statementCount = throwIndex + 1;
- }
- }
-
- // handle the all statements in block excluding the last one
- if (statementCount > 1)
- {
- for (var i = 0; i < statementCount - 1; i++)
- {
- var stExpr = statementExprs[i];
- if (stExpr.NodeType == ExpressionType.Default && stExpr.Type == typeof(void))
- continue;
-
- // This is basically the return pattern (see #237), so we don't care for the rest of expressions
- if (stExpr is GotoExpression gt && gt.Kind == GotoExpressionKind.Return &&
- statementExprs[i + 1] is LabelExpression label && label.Target == gt.Target)
- {
- // But we cannot use the return pattern and eliminate the target label if we have more gotos referencing it, see #430
- var (gotos, labels) = closure.TargetToGotosAndLabels.Map.TryGetValueRef(label.Target, out var found);
- if (found & gotos <= labels)
- {
- if ((parent & ParentFlags.TryCatch) != 0)
- {
- if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0)
- throw new NotSupportedExpressionException(Result.NotSupported_Try_GotoReturnToTheFollowupLabel);
- return false; // todo: @feature return from the TryCatch with the internal label is not supported, though it is an unlikely case
- }
-
- // we are generating the return value and ensuring here that it is not popped-out
- var gtOrLabelValue = gt.Value ?? label.DefaultValue;
- if (gtOrLabelValue != null)
- {
- if (!TryEmit(gtOrLabelValue, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult))
- return false;
-
- if ((parent & ParentFlags.InlinedLambdaInvoke) != 0)
- {
- ref var foundLabel = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(gt.Target, out var labelFound);
- if (!labelFound || foundLabel.InlinedLambdaInvokeIndex == -1)
- return false;
- EmitGotoToReturnLabel(ref closure.LambdaInvokeStackLabels.GetSurePresentRef(foundLabel.InlinedLambdaInvokeIndex), il, gtOrLabelValue, OpCodes.Br);
- }
- else
- {
- // @hack (related to #237) if `IgnoreResult` set, that means the external/calling code won't planning on returning and
- // emitting the double `OpCodes.Ret` (usually for not the last statement in block), so we can safely emit our own `Ret` here.
- // And vice-versa, if `IgnoreResult` not set then the external code planning to emit `Ret` (the last block statement),
- // so we should avoid it on our side.
- if ((parent & ParentFlags.IgnoreResult) != 0)
- il.Demit(OpCodes.Ret);
- }
- }
- return true;
- }
- }
-
- if (!TryEmit(stExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult))
- return false;
- }
- }
-
- parent |= ParentFlags.BlockResult;
- if (blockVarCount == 0)
- continue; // OMG! no recursion, continue with the last expression
-
- if (!TryEmit(expr, paramExprs, il, ref closure, setup, parent))
- return false;
-
- closure.PopBlock();
- return true;
- }
- case ExpressionType.Loop:
- return TryEmitLoop((LoopExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Try:
- return TryEmitTryCatchFinallyBlock((TryExpression)expr, paramExprs, il, ref closure, setup, parent | ParentFlags.TryCatch);
-
- case ExpressionType.Throw:
- {
- 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:
- if (exprType != typeof(void) && (parent & ParentFlags.IgnoreResult) == 0)
- EmitDefault(il, exprType);
- return true;
-
- case ExpressionType.Index:
- return TryEmitIndexGet((IndexExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Goto:
- return TryEmitGoto((GotoExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Label:
- return TryEmitLabel((LabelExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Switch:
- return TryEmitSwitch((SwitchExpression)expr, paramExprs, il, ref closure, setup, parent);
-
- case ExpressionType.Extension:
- expr = expr.Reduce();
- continue;
-
- case ExpressionType.DebugInfo: // todo: @feature - is not supported yet
- return true; // todo: @unclear - just ignoring the info for now
-
- case ExpressionType.Quote: // todo: @feature - is not supported yet
- default:
- return false;
-
- }
- }
- }
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitNew(Expression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitNew(Expression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- var flags = ParentFlags.CtorCall;
- var newExpr = (NewExpression)expr;
-#if SUPPORTS_ARGUMENT_PROVIDER
- var argExprs = (IArgumentProvider)newExpr;
-#else
- var argExprs = newExpr.Arguments;
-#endif
- var argCount = argExprs.GetCount();
- var ctor = newExpr.Constructor;
- if (argCount != 0)
- {
- var pars = ctor.GetParameters();
-
- // If we have complex arguments then it is better to store them in the variables, then load them before calling ctor.
- // Otherwise the stack may be broken by the long emit chain, and the previous argument result on stack
- // may be hidden by the next argument interim stack additions,
- // see the #488 for the details.
- if (argCount == 1)
- {
- if (!TryEmit(argExprs.GetArgument(0), paramExprs, il, ref closure, setup, flags, pars[0].ParameterType.IsByRef ? 0 : -1))
- return false;
- }
- else
- {
- if (!closure.ArgsContainingComplexExpression.Map.ContainsKey(newExpr))
- {
- for (var i = 0; i < argCount; ++i)
- if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, flags, pars[i].ParameterType.IsByRef ? i : -1))
- return false;
- }
- else
- {
- SmallList, NoArrayPool> argVars = default;
- for (var i = 0; i < argCount; ++i)
- {
- var argExpr = argExprs.GetArgument(i);
- var parType = pars[i].ParameterType;
- if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1))
- return false;
- argVars.Add(EmitStoreLocalVariable(il, parType));
- }
- for (var i = 0; i < argCount; ++i)
- EmitLoadLocalVariable(il, argVars[i]);
- }
- }
- }
- var newType = newExpr.Type;
- // ReSharper disable once ConditionIsAlwaysTrueOrFalse
- if (ctor != null)
- il.Demit(OpCodes.Newobj, ctor);
- else if (newType.IsValueType)
- {
- ctor = newType.GetConstructor(
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- default, CallingConventions.Any, Tools.Empty(), default);
- if (ctor != null)
- il.Demit(OpCodes.Newobj, ctor);
- else
- EmitLoadLocalVariable(il, InitValueTypeVariable(il, newType));
- }
- else
- return false;
-
- closure.LastEmitIsAddress =
- (parent & ParentFlags.InstanceAccess) != 0 & (parent & ParentFlags.IgnoreResult) == 0 && newType.IsValueType;
- if (closure.LastEmitIsAddress)
- EmitStoreAndLoadLocalVariableAddress(il, newType);
-
- if (parent.IgnoresResult())
- il.Demit(OpCodes.Pop);
-
- return true;
- }
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitLoop(LoopExpression loopExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitLoop(LoopExpression loopExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- // Loop expression itself does not leave a value on stack.
- // If its body produces a value (e.g. a nested typed break/goto path), we need to ignore it before branching back to the loop head, see #498.
- parent |= ParentFlags.IgnoreResult;
-
- // Mark the start of the loop body:
- var loopBodyLabel = il.DefineLabel();
- il.DmarkLabel(loopBodyLabel);
-
- if (loopExpr.ContinueLabel != null)
- {
- ref var continueLabelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.ContinueLabel, out var foundLabel);
- if (!foundLabel)
- return false;
- var continueLabel = continueLabelInfo.GetOrDefineLabel(il);
- il.DmarkLabel(continueLabel);
- }
-
- if (!TryEmit(loopExpr.Body, paramExprs, il, ref closure, setup, parent))
- return false;
-
- // If loop hasn't exited, jump back to start of its body:
- il.Demit(OpCodes.Br, loopBodyLabel);
-
- if (loopExpr.BreakLabel != null)
- {
- ref var breakLabelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.BreakLabel, out var foundLabel);
- if (!foundLabel)
- return false;
- var breakLabel = breakLabelInfo.GetOrDefineLabel(il);
- il.DmarkLabel(breakLabel);
- }
-
- return true;
- }
-
- // similar code is used by the TryEmitArithmeticAndOrAssign, so don't forget to modify it as well
- private static bool TryEmitIndexGet(IndexExpression indexExpr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- var p = parent & ~ParentFlags.IgnoreResult | ParentFlags.IndexAccess;
-
- var objExpr = indexExpr.Object;
- if (objExpr != null &&
- !TryEmit(objExpr, paramExprs, il, ref closure, setup, p | ParentFlags.InstanceAccess))
- return false;
-
-#if SUPPORTS_ARGUMENT_PROVIDER
- var indexArgs = (IArgumentProvider)indexExpr;
-#else
- var indexArgs = indexExpr.Arguments;
-#endif
- var indexArgCount = indexArgs.GetCount();
- // Strip InstanceAccess when emitting arguments: they are parameters to the indexer
- // getter method, not instances. Without this, a nested IndexExpression like
- // `list[i][j]` leaks the outer InstanceAccess into the inner indexer's argument
- // emission, causing value-type args (e.g. int) to be loaded by address (ldloca)
- // instead of by value (ldloc), producing invalid IL. See #499.
- var argParent = p & ~ParentFlags.InstanceAccess;
- for (var i = 0; i < indexArgCount; i++)
- if (!TryEmit(indexArgs.GetArgument(i), paramExprs, il, ref closure, setup, argParent, -1))
- return false;
-
- var indexerProp = indexExpr.Indexer;
- return indexerProp != null
- ? EmitMethodCallOrVirtualCallCheckForNull(il, indexerProp.GetMethod)
- : indexArgCount == 1
- ? TryEmitArrayIndexGet(il, indexExpr.Type, ref closure, parent) // one-dimensional array
- : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array
- }
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitLabel(LabelExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitLabel(LabelExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- ref var labelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var foundLabel);
- if (!foundLabel)
- return false;
-
- var label = labelInfo.GetOrDefineLabel(il);
- il.DmarkLabel(label);
-
- var defaultValue = expr.DefaultValue;
- if (defaultValue != null && !TryEmit(defaultValue, paramExprs, il, ref closure, setup, parent))
- return false;
-
- var returnVariableIndexPlusOne = labelInfo.ReturnVariableIndexPlusOneAndIsDefined >>> 1;
- if (returnVariableIndexPlusOne != 0)
- {
- // if the result value was ignored then we did not IL generate anything for the default value,
- // and that in order means we do not have valid thing on stack to store here - so skip the store
- if (defaultValue != null & ((parent & ParentFlags.IgnoreResult) == 0))
- EmitStoreLocalVariable(il, returnVariableIndexPlusOne - 1);
-
- il.DmarkLabel(labelInfo.ReturnLabel);
- if (!parent.IgnoresResult())
- EmitLoadLocalVariable(il, returnVariableIndexPlusOne - 1);
- }
- return foundLabel;
- }
-
- // For TryCatch get the variable for saving the result from the LabelInfo store the return expression result into the that variable.
- // Emit OpCodes.Leave or OpCodes.Br to the special label with the result which should be marked after the label to jump over its default value
- private static void EmitGotoToReturnLabel(ref LabelInfo labelInfo, ILGenerator il, Expression gotoValue, OpCode returnOpCode)
- {
- var returnVariableIndexPlusOne = labelInfo.ReturnVariableIndexPlusOneAndIsDefined >>> 1;
- if (returnVariableIndexPlusOne == 0)
- {
- returnVariableIndexPlusOne = il.GetNextLocalVarIndex(gotoValue.Type) + 1;
- labelInfo.ReturnVariableIndexPlusOneAndIsDefined = (short)(returnVariableIndexPlusOne << 1);
- labelInfo.ReturnLabel = il.DefineLabel();
- }
- EmitStoreLocalVariable(il, returnVariableIndexPlusOne - 1);
- il.Demit(returnOpCode, labelInfo.ReturnLabel);
- }
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitGoto(GotoExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitGoto(GotoExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- ref var labelInfo = ref closure.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var labelFound);
- if (!labelFound)
- {
- if ((closure.Status & ClosureStatus.ToBeCollected) == 0)
- return false; // if no collection cycle then the labels may be not collected
- throw new InvalidOperationException($"Cannot jump, no labels found for the target `{expr.Target}`");
- }
- var gotoValue = expr.Value;
- if (gotoValue != null &&
- !TryEmit(gotoValue, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult))
- return false;
-
- switch (expr.Kind)
- {
- case GotoExpressionKind.Break:
- case GotoExpressionKind.Continue:
- il.Demit(OpCodes.Br, labelInfo.GetOrDefineLabel(il));
- return true;
-
- case GotoExpressionKind.Goto:
- if (gotoValue != null)
- goto case GotoExpressionKind.Return;
-
- var gotoOpCode = (parent & ParentFlags.TryCatch) != 0 ? OpCodes.Leave : OpCodes.Br;
- il.Demit(gotoOpCode, labelInfo.GetOrDefineLabel(il));
- return true;
-
- case GotoExpressionKind.Return:
- if ((parent & ParentFlags.TryCatch) != 0)
- {
- if (gotoValue != null)
- EmitGotoToReturnLabel(ref labelInfo, il, gotoValue, OpCodes.Leave);
- else
- il.Demit(OpCodes.Leave, labelInfo.GetOrDefineLabel(il)); // if there is no return value just leave to the original label
- }
- else if ((parent & ParentFlags.InlinedLambdaInvoke) != 0)
- {
- if (gotoValue != null)
- {
- var invokeIndex = labelInfo.InlinedLambdaInvokeIndex;
- if (invokeIndex == -1)
- return false;
- EmitGotoToReturnLabel(ref closure.LambdaInvokeStackLabels.GetSurePresentRef(invokeIndex), il, gotoValue, OpCodes.Br);
- }
- }
- else
- il.Demit(OpCodes.Ret);
- return true;
-
- default:
- return false;
- }
- }
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitCoalesceOperator(BinaryExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitCoalesceOperator(BinaryExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- var labelFalse = il.DefineLabel(); // todo: @perf define only if needed
- var labelDone = il.DefineLabel();
-
- var left = expr.Left;
- var right = expr.Right;
-
- // we won't OpCodes.Pop inside the Coalesce as it may leave the Il in invalid state - instead we will pop at the end here (#284)
- var flags = (parent & ~ParentFlags.IgnoreResult) | ParentFlags.Coalesce;
-
- if (!TryEmit(left, paramExprs, il, ref closure, setup, flags))
- return false;
-
- var exprType = expr.Type;
- var leftType = left.Type;
- if (leftType.IsValueType)
- {
- Debug.Assert(leftType.IsNullable(), "Expecting Nullable, it is the only ValueType comparable to null");
-
- var varIndex = EmitStoreAndLoadLocalVariableAddress(il, leftType);
- EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod());
-
- il.Demit(OpCodes.Brfalse, labelFalse);
-
- if (exprType == Nullable.GetUnderlyingType(leftType))
- {
- // if the target expression type is of underlying nullable, and the left operand is not null,
- // then extract its underlying value
- EmitLoadLocalVariableAddress(il, varIndex);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- }
- else
- EmitLoadLocalVariable(il, varIndex); // loading the value (not address) to return it
-
- il.Demit(OpCodes.Br, labelDone);
- il.DmarkLabel(labelFalse);
- if (!TryEmit(right, paramExprs, il, ref closure, setup, flags))
- return false;
-
- il.DmarkLabel(labelDone);
- }
- else
- {
- il.Demit(OpCodes.Dup); // duplicate left, if it's not null, after the branch this value will be on the top of the stack
- il.Demit(OpCodes.Brtrue, labelFalse); // automates the chain of the Ldnull, Ceq, Brfalse
- il.Demit(OpCodes.Pop); // left is null, pop its value from the stack
-
- if (!TryEmit(right, paramExprs, il, ref closure, setup, flags))
- return false;
-
- if (right.Type != exprType)
- il.TryEmitBoxOf(right.Type);
-
- if (left.Type == exprType)
- il.DmarkLabel(labelFalse);
- else
- {
- il.Demit(OpCodes.Br, labelDone);
- il.DmarkLabel(labelFalse); // todo: @bug? should we insert the boxing for the Nullable value type before the Castclass
- il.Demit(OpCodes.Castclass, exprType);
- il.DmarkLabel(labelDone);
- }
- }
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- /// Emit default op code for the type
- public static void EmitDefault(ILGenerator il, Type type)
- {
- if (type.IsClass)
- {
- il.Demit(OpCodes.Ldnull);
- return;
- }
- switch (Type.GetTypeCode(type))
- {
- case TypeCode.Boolean:
- case TypeCode.Char:
- case TypeCode.Byte:
- case TypeCode.SByte:
- case TypeCode.Int16:
- case TypeCode.UInt16:
- case TypeCode.Int32:
- case TypeCode.UInt32:
- il.Demit(OpCodes.Ldc_I4_0);
- break;
- case TypeCode.Int64:
- case TypeCode.UInt64:
- il.Demit(OpCodes.Ldc_I4_0);
- il.Demit(OpCodes.Conv_I8);
- break;
- case TypeCode.Single:
- il.Demit(OpCodes.Ldc_R4, default(float));
- break;
- case TypeCode.Double:
- il.Demit(OpCodes.Ldc_R8, default(double));
- break;
- default:
- EmitLoadLocalVariable(il, InitValueTypeVariable(il, type));
- break;
- }
- }
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
-#if DEMIT
- Debug.WriteLine("try {");
-#endif
- il.BeginExceptionBlock();
-
- if (!TryEmit(tryExpr.Body, paramExprs, il, ref closure, setup, parent))
- return false;
-
- var exprType = tryExpr.Type;
- var returnsResult = exprType != typeof(void) && !parent.IgnoresResult();
- var resultVarIndex = -1;
-
- if (returnsResult)
- EmitStoreLocalVariable(il, resultVarIndex = il.GetNextLocalVarIndex(exprType));
-
- var catchBlocks = tryExpr.Handlers;
- for (var i = 0; i < catchBlocks.Count; i++)
- {
- var catchBlock = catchBlocks[i];
- if (catchBlock.Filter != null)
- {
- if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0)
- throw new NotSupportedExpressionException(Result.NotSupported_ExceptionCatchFilter);
- return false;
- }
-
- il.BeginCatchBlock(catchBlock.Test);
-
- // at the beginning of catch the Exception value is on the stack,
- // we will store into local variable.
- var exVarExpr = catchBlock.Variable;
-#if DEMIT
- Debug.WriteLine($"}} catch {{");
-#endif
- if (exVarExpr != null)
- {
- // first, check if the exception variable was used before and supposed to be reused in the new catch
- // (this is decided by creator of expression)
- var exVarIndex = closure.GetDefinedLocalVarOrDefault(exVarExpr);
- if (exVarIndex == -1)
- exVarIndex = il.GetNextLocalVarIndex(exVarExpr.Type);
- closure.PushBlockWithVars(exVarExpr, exVarIndex);
- EmitStoreLocalVariable(il, exVarIndex);
- }
-
- if (!TryEmit(catchBlock.Body, paramExprs, il, ref closure, setup, parent))
- return false;
-
- if (exVarExpr != null)
- closure.PopBlock();
-
- if (returnsResult)
- EmitStoreLocalVariable(il, resultVarIndex);
- }
-
- if (tryExpr.Fault != null)
- {
- var faultExpr = tryExpr.Fault;
-#if DEMIT
- Debug.WriteLine("} fault {" + faultExpr);
-#endif
- il.BeginFaultBlock();
- // it is important to ignore result for the fault block, because it should not return anything
- if (!TryEmit(faultExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult))
- return false;
- }
- else if (tryExpr.Finally != null)
- {
- var finallyExpr = tryExpr.Finally;
-#if DEMIT
- Debug.WriteLine("} finally {" + finallyExpr);
-#endif
- il.BeginFinallyBlock();
- // it is important to ignore result for the finally block, because it should not return anything
- if (!TryEmit(finallyExpr, paramExprs, il, ref closure, setup, parent | ParentFlags.IgnoreResult))
- return false;
- }
-
- il.EndExceptionBlock();
-
-#if DEMIT
- Debug.WriteLine("}");
-#endif
-
- if (returnsResult)
- EmitLoadLocalVariable(il, resultVarIndex);
-
- return true;
- }
-
- public static bool TryEmitParameter(ParameterExpression paramExpr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1)
- {
- var paramExprCount = paramExprs.GetCount();
- var paramType = paramExpr.Type;
- var isValueType = paramType.IsValueType;
- var isParamOrVarByRef = paramExpr.IsByRef && !paramType.IsNullable(); // for the nullable part check the #461 cases with nullable
- var isPassedRef = byRefIndex != -1;
-
- // Parameter may represent a variable, so first look if this is the case,
- // and the variable is defined in the current block
- var varIndex = closure.GetDefinedLocalVarOrDefault(paramExpr);
- if (varIndex != -1)
- {
- // todo: @perf analyze if the variable is actually may be mutated in the nested lambda by being assigned or passed by ref
- // Check if the variable is passed to the nested closure (#437), so it should be loaded from the nested closure NonPassedParams array
- var nestedLambdasCount = closure.NestedLambdas.Count;
- if (nestedLambdasCount != 0)
- {
- var nestedLambdas = closure.NestedLambdas.Items;
- for (var nestedLambdaIndex = 0; nestedLambdaIndex < nestedLambdasCount; ++nestedLambdaIndex)
- {
- var lambdaInfo = nestedLambdas[nestedLambdaIndex];
- var nonPassedParamCount = lambdaInfo.NonPassedParameters.Count;
- if (nonPassedParamCount != 0)
- {
- var varIndexInNonPassedParams = lambdaInfo.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq));
- if (varIndexInNonPassedParams != -1 &&
- (lambdaInfo.NonPassedParamMutatedIndexBits & (1UL << varIndexInNonPassedParams)) != 0)
- {
- // Load the nested lambda item from the closure constants and nested lambdas array
- var closureArrayItemIndex = closure.Constants.Count + nestedLambdaIndex;
- EmitLoadClosureArrayItem(il, closureArrayItemIndex);
-
- // Check if the NonPassedArray field is being set (not null),
- // otherwise the nested lambda is not yet emitted, and it is not expected for the variable to be set inside it
- il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParams.NonPassedParamsField);
-
- // Load the variable from the NonPassedParams array
- EmitLoadConstantInt(il, varIndexInNonPassedParams);
- il.Demit(OpCodes.Ldelem_Ref);
- il.TryEmitUnboxOf(paramType);
- return true;
- }
- }
- }
- }
-
- var valueTypeMemberButNotIndexAccess = isValueType &
- // means the parameter is the instance for the called method or the instance for the member access, see #274, #283
- (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0 &
- (
- // but the variable is not used as an index for the method call or the member access
- // `a[i].Foo()` -> false, see #281
- // `a[i].Bar` -> false, see #265
- // `a[i]` -> true, see #413
- (parent & ParentFlags.IndexAccess) == 0 |
- (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) == 0
- );
-
- closure.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess);
-
- if (closure.LastEmitIsAddress)
- EmitLoadLocalVariableAddress(il, varIndex);
- else if (!isParamOrVarByRef)
- EmitLoadLocalVariable(il, varIndex);
- else
- {
- if (isParamOrVarByRef & isValueType &
- (parent & ParentFlags.BlockResult) != 0 &
- (parent & (ParentFlags.MemberAccess | ParentFlags.Call | ParentFlags.InstanceAccess | ParentFlags.Arithmetic)) == 0)
- {
- EmitLoadIndirectlyByRef(il, paramType);
- return true;
- }
-
- var byAddress = isParamOrVarByRef & isPassedRef & isValueType;
- if (byAddress)
- EmitStoreAndLoadLocalVariableAddress(il, varIndex);
- else if ((parent & ParentFlags.InstanceCall) == ParentFlags.InstanceCall)
- EmitLoadLocalVariable(il, varIndex);
- else
- EmitStoreAndLoadLocalVariable(il, varIndex);
-
- // Assume that the var is the last on the stack and just duplicating it, see #346 `Get_array_element_ref_and_increment_it`
- // Do only when accessing the variable directly, and not the member and the array element by index
- if (byAddress)
- il.Demit(OpCodes.Dup);
- else if ((parent & ParentFlags.InstanceAccess) == 0)
- EmitLoadLocalVariable(il, varIndex);
-
- if (isValueType)
- {
- if ((parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0 &
- (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) == 0)
- EmitLoadIndirectlyByRef(il, paramType);
- }
- else
- {
- if ((parent & (ParentFlags.Coalesce | ParentFlags.MemberAccess | ParentFlags.IndexAccess | ParentFlags.AssignmentRightValue)) != 0)
- il.Demit(OpCodes.Ldind_Ref);
- }
- }
- return true;
- }
-
- // If not variable then look if the parameter is passed to the current lambda
- var paramIndex = paramExprCount - 1;
- while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), paramExpr)) --paramIndex;
- if (paramIndex != -1)
- {
- if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0)
- ++paramIndex; // shift parameter index by one, because the first one will be closure
-
- // means the parameter is the instance for what method is called or the instance for the member access, see #274, #283
- var valueTypeMemberButNotIndexAccess = isValueType &
- // means the parameter is the instance for what method is called or the instance for the member access, see #274, #283
- (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) != 0 &
- // see #352 for the arithmetic
- (parent & ParentFlags.Arithmetic) == 0 &
- (
- // but the variable is not used as an index for the method call or the member access
- // `a[i].Foo()` -> false, see #281
- // `a[i].Bar` -> false, see #265
- // `a[i]` -> true, see #413
- (parent & ParentFlags.IndexAccess) == 0 |
- (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) == 0
- );
-
- closure.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess);
-
- if (closure.LastEmitIsAddress)
- EmitLoadArgAddress(il, paramIndex);
- else
- EmitLoadArg(il, paramIndex);
-
- // todo: @simplify as it is complex overall and EmitLoadIndirectlyByRef does the Ldind_Ref too
- if (isParamOrVarByRef)
- {
- // todo: @wip requires a cleanup
- if (isValueType)
- {
- // #248 - skip the cases with `ref param.Field` were we are actually want to load the `Field` address not the `param`
- // this means the parameter is the argument to the method call and not the instance in the method call or member access
- if (!isPassedRef & (
- ((parent & ParentFlags.Call) != 0 & (parent & ParentFlags.InstanceAccess) == 0) |
- ((parent & ParentFlags.LambdaCall) != 0 & (parent & ParentFlags.ReturnByRef) == 0)
- ) ||
- (parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0 &
- (parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess | ParentFlags.AssignmentLeftValue)) == 0)
- EmitLoadIndirectlyByRef(il, paramType);
- }
- else
- {
- if (!isPassedRef & (
- (parent & (ParentFlags.Call | ParentFlags.LambdaCall)) != 0) ||
- (parent & (ParentFlags.Coalesce | ParentFlags.MemberAccess | ParentFlags.IndexAccess | ParentFlags.AssignmentRightValue)) != 0)
- il.Demit(OpCodes.Ldind_Ref);
- }
- }
- return true;
- }
-
- if (isParamOrVarByRef)
- {
- EmitLoadLocalVariableAddress(il, byRefIndex); // todo: @bug? `closure.LastEmitIsAddress = true;` should we do it too as in above code with the variable
- return true;
- }
-
- // the only possibility that we are here is because we are in the nested lambda,
- // and it uses the parameter or variable from the outer lambda
- var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq));
- if (nonPassedParamIndex == -1)
- return false;
-
- // Load non-passed argument from Closure - closure object is always a first argument
- il.Demit(OpCodes.Ldarg_0);
- il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
- EmitLoadConstantInt(il, nonPassedParamIndex);
- il.Demit(OpCodes.Ldelem_Ref);
- return il.TryEmitUnboxOf(paramType);
- }
-
-#if LIGHT_EXPRESSION
- public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure)
- {
- var paramExprCount = paramExprs.ParameterCount;
-#else
- public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure)
- {
- var paramExprCount = paramExprs.Count;
-#endif
- // if parameter is passed through, then just load it on stack
- var paramType = paramExpr.Type;
- var paramIndex = paramExprCount - 1;
- while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), paramExpr))
- --paramIndex;
- if (paramIndex != -1)
- {
- if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0)
- ++paramIndex; // shift parameter index by one, because the first one will be closure
- if (closure.LastEmitIsAddress)
- EmitLoadArgAddress(il, paramIndex);
- else
- EmitLoadArg(il, paramIndex);
- return true;
- }
-
- // the only possibility that we are here is because we are in the nested lambda,
- // and it uses the parameter or variable from the outer lambda
- var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(paramExpr, default(RefEq));
- if (nonPassedParamIndex == -1)
- return false;
-
- // Load non-passed argument from Closure - closure object is always a first argument
- il.Demit(OpCodes.Ldarg_0);
- il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
- EmitLoadConstantInt(il, nonPassedParamIndex);
- il.Demit(OpCodes.Ldelem_Ref);
- return true;
- }
-
- private static bool TryEmitSimpleUnaryExpression(UnaryExpression expr, ExpressionType nodeType,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- var exprType = expr.Type;
-
- if (!TryEmit(expr.Operand, paramExprs, il, ref closure, setup, parent))
- return false;
-
- if (nodeType == ExpressionType.TypeAs)
- {
- il.TryEmitBoxOf(expr.Operand.Type);
- il.Demit(OpCodes.Isinst, exprType);
- if (exprType.IsValueType)
- il.Demit(OpCodes.Unbox_Any, exprType);
- }
- else if (nodeType == ExpressionType.IsFalse)
- {
- var falseLabel = il.DefineLabel();
- var continueLabel = il.DefineLabel();
- il.Demit(OpCodes.Brfalse, falseLabel);
- il.Demit(OpCodes.Ldc_I4_0);
- il.Demit(OpCodes.Br, continueLabel);
- il.DmarkLabel(falseLabel);
- il.Demit(OpCodes.Ldc_I4_1);
- il.DmarkLabel(continueLabel);
- }
- else if (nodeType == ExpressionType.Increment)
- {
- if (exprType.IsPrimitive)
- {
- if (!TryEmitNumberOne(il, exprType))
- return false;
- il.Demit(OpCodes.Add);
- }
- else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_Increment")))
- return false;
- }
- else if (nodeType == ExpressionType.Decrement)
- {
- if (exprType.IsPrimitive)
- {
- if (!TryEmitNumberOne(il, exprType))
- return false;
- il.Demit(OpCodes.Sub);
- }
- else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_Decrement")))
- return false;
- }
- else if (nodeType == ExpressionType.Negate | nodeType == ExpressionType.NegateChecked)
- {
- if (exprType.IsPrimitive)
- il.Demit(OpCodes.Neg);
- else if (!EmitMethodCallCheckForNull(il, exprType.GetMethod("op_UnaryNegation")))
- return false;
- }
- else if (nodeType == ExpressionType.OnesComplement)
- il.Demit(OpCodes.Not);
- else if (nodeType == ExpressionType.Unbox)
- il.Demit(OpCodes.Unbox_Any, exprType);
- // else if (nodeType == ExpressionType.IsTrue) { }
- // else if (nodeType == ExpressionType.UnaryPlus) { }
-
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- private static bool TryEmitTypeIsOrEqual(TypeBinaryExpression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- if (!TryEmit(expr.Expression, paramExprs, il, ref closure, setup, parent))
- return false;
- if ((parent & ParentFlags.IgnoreResult) != 0)
- return true;
- if (expr.NodeType == ExpressionType.TypeIs)
- {
- il.Demit(OpCodes.Isinst, expr.TypeOperand);
- il.Demit(OpCodes.Ldnull);
- il.Demit(OpCodes.Cgt_Un);
- return true;
- }
- if ((setup & CompilerFlags.ThrowOnNotSupportedExpression) != 0)
- throw new NotSupportedExpressionException(Result.NotSupported_TypeEqual);
- return false;
- }
-
- private static bool TryEmitNot(UnaryExpression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- var op = expr.Operand;
- if (op.NodeType == ExpressionType.Equal)
- return TryEmitComparison(((BinaryExpression)op).Left, ((BinaryExpression)op).Right,
- expr.Type, ExpressionType.NotEqual, paramExprs, il, ref closure, setup, parent);
-
- if (!TryEmit(op, paramExprs, il, ref closure, setup, parent))
- return false;
-
- if ((parent & ParentFlags.IgnoreResult) != 0)
- il.Demit(OpCodes.Pop);
- else if (expr.Type == typeof(bool))
- EmitEqualToZeroOrNull(il);
- else
- il.Demit(OpCodes.Not);
- return true;
- }
-
- private static bool TryEmitConvert(UnaryExpression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- var opExpr = expr.Operand;
- var sourceType = opExpr.Type;
- var targetType = expr.Type;
- var underlyingNullableSourceType = Nullable.GetUnderlyingType(sourceType);
- var underlyingNullableTargetType = Nullable.GetUnderlyingType(targetType);
-
- var convertMethod = expr.Method;
- if (convertMethod == null)
- {
- // Try fast the special cases which does not require searching for the conversion operators in principle:
- if (parent.IgnoresResult() && (sourceType == targetType || targetType.IsAssignableFrom(sourceType)))
- return TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.InstanceAccess);
-
- // Emit the operand before going to the fast checks below
- if (!TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess))
- return false;
-
- if (sourceType == targetType)
- return il.EmitPopIfIgnoreResult(parent);
-
- if (targetType == underlyingNullableSourceType)
- {
- if (!closure.LastEmitIsAddress)
- EmitStoreAndLoadLocalVariableAddress(il, sourceType);
- EmitMethodCall(il, sourceType.GetNullableValueGetterMethod());
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (sourceType == underlyingNullableTargetType)
- {
- il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor());
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (targetType == typeof(object) && sourceType.IsValueType)
- {
- il.Demit(OpCodes.Box, sourceType);
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (sourceType == typeof(object) && targetType.IsValueType ||
- sourceType == typeof(Enum) && targetType.IsEnum // a special case, see the AutoMapper test `StringToEnumConverter.Should_work`
- )
- {
- il.Demit(OpCodes.Unbox_Any, targetType);
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- // At least just check the assignability of the source to the target type,
- // check only after the checks above for the ValueType or object Type,
- // because their require additional boxing/unboxing operations
- if (targetType.IsAssignableFrom(sourceType))
- {
- if (sourceType.IsValueType && !targetType.IsValueType)
- il.Demit(OpCodes.Box, sourceType);
- il.Demit(OpCodes.Castclass, targetType);
- return il.EmitPopIfIgnoreResult(parent);
- }
- }
-
- // Check implicit / explicit conversion operators on source and then on the target type,
- // check their underlying nullable types, because Nullable does not contain conversion ops,
- // also check inside that it is not primitive type, nor string or enum because those do no contain the conversion operators either.
- // see #73, #451 for examples
- Type methodReturnType = null;
- Type methodParamType = null;
- if (convertMethod != null)
- {
- methodReturnType = convertMethod.ReturnType;
-
- var mParams = convertMethod.GetParameters();
- Debug.Assert(mParams.Length == 1, $"Expected for the conversion operator to have a single param, but found {mParams.Length}");
- methodParamType = mParams[0].ParameterType;
-
- // todo: @wip check if we need to add the ParentFlags.Call here?
- if (!TryEmit(opExpr, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess))
- return false;
- }
- else
- {
- // Lookup for the conversion method first in sourceType then in the targetType
- Type underlyingOrNullableSourceType = null;
- for (var lookupCount = 0; lookupCount < 2 & convertMethod == null; ++lookupCount)
- {
- var convOwnerType = lookupCount == 0
- ? underlyingNullableSourceType ?? sourceType
- : underlyingNullableTargetType ?? targetType;
-
- if (convOwnerType == typeof(string) || convOwnerType.IsEnum || convOwnerType.IsPrimitive)
- continue;
-
- var staticMethods = convOwnerType.GetMethods(BindingFlags.Static | BindingFlags.Public);
- foreach (var m in staticMethods)
- {
- if (!m.IsSpecialName)
- continue;
-
- // Method return type should be convertible to target type,
- // and therefore it does not check for the method return type of Nullable
- // because it cannot be coalesced to targetType without loss of information
- methodReturnType = m.ReturnType;
- if (methodReturnType != targetType && methodReturnType != underlyingNullableTargetType ||
- m.Name != "op_Implicit" && m.Name != "op_Explicit")
- continue;
-
- var operatorParams = m.GetParameters();
- Debug.Assert(operatorParams.Length == 1, $"Expected for the conversion operator to have a single param, but found {operatorParams.Length}");
-
- methodParamType = operatorParams[0].ParameterType;
- if (methodParamType == sourceType)
- {
- convertMethod = m;
- break;
- }
-
- // This next check is only valid if the source type is the ValueType, so it can be either a Nullable struct or it can be wrapped in the Nullable
- if (sourceType.IsValueType)
- {
- // Check for all variants of the source type which maybe either underlying nullable or nullable of the source type
- // Calculate it once because less work is better.
- underlyingOrNullableSourceType ??= underlyingNullableSourceType ?? sourceType.GetNullable();
- if (methodParamType == underlyingOrNullableSourceType)
- {
- convertMethod = m;
- break;
- }
- }
- }
- }
- }
-
- if (convertMethod != null)
- {
- Debug.Assert(methodParamType != null & methodReturnType != null,
- "Expecting that actual source and return type are set for the found conversion operator method");
-
- // For the both nullable source and target types,
- // first check the source value for null and return null without calling the conversion method, otherwise call the conversion method
- if (methodParamType == underlyingNullableSourceType & underlyingNullableTargetType != null)
- {
- var sourceVarIndex = EmitStoreAndLoadLocalVariableAddress(il, sourceType);
- EmitMethodCall(il, sourceType.GetNullableHasValueGetterMethod());
-
- var labelSourceHasValue = il.DefineLabel();
- il.Demit(OpCodes.Brtrue_S, labelSourceHasValue); // Jump to this label when the source has a value
-
- // Otherwise, emit and load the default nullable target without value, e.g. `default(Nullable)`
- // then... the conversion is completed, so jumping to the done label
- EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType));
- var labelConversionDone = il.DefineLabel();
- il.Demit(OpCodes.Br_S, labelConversionDone);
-
- // If the nullable source has the value, do the conversion
- il.DmarkLabel(labelSourceHasValue);
- EmitLoadLocalVariableAddress(il, sourceVarIndex);
- il.Demit(OpCodes.Ldfld, sourceType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
-
- EmitMethodCallOrVirtualCall(il, convertMethod);
-
- // Wrap the conversion result into the target type only if the conversion method return the underlying target type,
- // otherwise we done
- if (methodReturnType == underlyingNullableTargetType)
- il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor());
-
- il.DmarkLabel(labelConversionDone);
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (methodParamType == underlyingNullableSourceType)
- {
- EmitStoreAndLoadLocalVariableAddress(il, sourceType);
- EmitMethodCall(il, sourceType.GetNullableValueGetterMethod());
- }
- else if (methodParamType != sourceType) // This is an unlikely case of Target(Source? source)
- {
- Debug.Assert(Nullable.GetUnderlyingType(methodParamType) == sourceType, "Expecting that the parameter type is the Nullable");
- il.Demit(OpCodes.Newobj, methodParamType.GetNullableConstructor());
- }
-
- EmitMethodCallOrVirtualCall(il, convertMethod);
-
- if (methodReturnType == underlyingNullableTargetType)
- il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor());
-
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (underlyingNullableSourceType != null & underlyingNullableTargetType != null)
- {
- var sourceVarIndex = EmitStoreAndLoadLocalVariableAddress(il, sourceType);
- EmitMethodCall(il, sourceType.GetNullableHasValueGetterMethod());
-
- var labelSourceHasValue = il.DefineLabel();
- il.Demit(OpCodes.Brtrue_S, labelSourceHasValue); // Jump here when the source has a value
-
- // Otherwise, emit and load the default nullable target without value, e.g. `default(Nullable)`
- // and... the conversion is completed, so jumping to the done label
- EmitLoadLocalVariable(il, InitValueTypeVariable(il, targetType));
- var labelConversionDone = il.DefineLabel();
- il.Demit(OpCodes.Br_S, labelConversionDone);
-
- // If the nullable source has the value, do the conversion
- il.DmarkLabel(labelSourceHasValue);
- EmitLoadLocalVariableAddress(il, sourceVarIndex);
- il.Demit(OpCodes.Ldfld, sourceType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
-
- if (!TryEmitPrimitiveValueConvert(underlyingNullableSourceType, underlyingNullableTargetType, il, expr.NodeType == ExpressionType.ConvertChecked))
- return false;
-
- il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor());
-
- // We done here.
- il.DmarkLabel(labelConversionDone);
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (underlyingNullableTargetType != null) // sourceType is NOT nullable here (checked above)
- {
- if (!underlyingNullableTargetType.IsEnum &&
- !TryEmitPrimitiveValueConvert(sourceType, underlyingNullableTargetType, il, isChecked: false))
- return false;
- il.Demit(OpCodes.Newobj, targetType.GetNullableConstructor());
- }
- else // targetType is NOT nullable here (checked above)
- {
- if (targetType.IsEnum)
- {
- targetType = Enum.GetUnderlyingType(targetType);
- if (targetType == sourceType)
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- // fixes #159
- if (underlyingNullableSourceType != null)
- {
- EmitStoreAndLoadLocalVariableAddress(il, sourceType);
- EmitMethodCall(il, sourceType.GetNullableValueGetterMethod());
- }
-
- // Cast as the last resort and let's it fail if unlucky
- if (!TryEmitPrimitiveValueConvert(underlyingNullableSourceType ?? sourceType, targetType, il, expr.NodeType == ExpressionType.ConvertChecked))
- {
- il.TryEmitBoxOf(sourceType);
- il.Demit(OpCodes.Castclass, targetType);
- }
- }
-
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- private static bool TryEmitPrimitiveValueConvert(Type sourceType, Type targetType, ILGenerator il, bool isChecked)
- {
- switch (Type.GetTypeCode(targetType))
- {
- case TypeCode.SByte:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_I1 : OpCodes.Conv_I1);
- break;
- case TypeCode.Byte:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_U1 : OpCodes.Conv_U1);
- break;
- case TypeCode.Int16:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_I2 : OpCodes.Conv_I2);
- break;
- case TypeCode.Int32:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_I4 : OpCodes.Conv_I4);
- break;
- case TypeCode.Int64:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_I8 : OpCodes.Conv_I8);
- break;
- case TypeCode.Double:
- if (sourceType.IsUnsigned())
- il.Demit(OpCodes.Conv_R_Un);
- il.Demit(OpCodes.Conv_R8);
- break;
- case TypeCode.Single:
- if (sourceType.IsUnsigned())
- il.Demit(OpCodes.Conv_R_Un);
- il.Demit(OpCodes.Conv_R4);
- break;
- case TypeCode.UInt16:
- case TypeCode.Char:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_U2 : OpCodes.Conv_U2);
- break;
- case TypeCode.UInt32:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4);
- break;
- case TypeCode.UInt64:
- il.Demit(isChecked ? OpCodes.Conv_Ovf_U8 : OpCodes.Conv_U8); // should we consider if sourceType.IsUnsigned == false and using the OpCodes.Conv_I8 (seems like the System.Compile does it)
- break;
- default:
- // todo: @feature for net7+ add Half, Int128, UInt128
- return false;
- }
- return true;
- }
-
- public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1)
- {
- var ok = false;
-#if LIGHT_EXPRESSION
- if (expr == NullConstant)
- {
- il.Demit(OpCodes.Ldnull);
- ok = true;
- }
- else if (expr == FalseConstant | expr == ZeroConstant)
- {
- il.Demit(OpCodes.Ldc_I4_0);
- ok = true;
- }
- else if (expr == TrueConstant | expr == OneConstant)
- {
- il.Demit(OpCodes.Ldc_I4_1);
- ok = true;
- }
- else if (expr is IntConstantExpression n)
- {
- EmitLoadConstantInt(il, (int)n.Value);
- ok = true;
- }
-#endif
- if (!ok)
- {
- 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 and yep, this is a proper way to emit the Nullable null
- ok = EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType));
- else
- {
- il.Demit(OpCodes.Ldnull);
- ok = true;
- }
- }
-
- if (ok & byRefIndex != -1)
- EmitStoreAndLoadLocalVariableAddress(il, exprType);
- return ok;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- 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)
- {
- if (exprType == null)
- exprType = constType;
-#if LIGHT_EXPRESSION
- if (considerClosure && (refField != null || IsClosureBoundConstant(constValue, constType)))
-#else
- if (considerClosure && IsClosureBoundConstant(constValue, constType))
-#endif
- {
- var constIndex = closure.Constants.TryGetIndex(constValue, default(RefEq));
- if (constIndex == -1)
- return false; // todo: @check should we throw an exception instead?
-
- var varIndex = closure.ConstantUsageThenVarIndex[constIndex] - 1;
- if (varIndex > 0)
- EmitLoadLocalVariable(il, varIndex);
- else
- {
- EmitLoadClosureArrayItem(il, constIndex);
-#if LIGHT_EXPRESSION
- // Handle the loading of the ref field if the constant usage is only once,
- // for the multiple usages the field was loaded and saved into variable in `EmitLoadConstantsAndNestedLambdasIntoVars`
- if (refField != null)
- {
- il.Demit(OpCodes.Ldfld, refField);
- if (refField.FieldType != typeof(object))
- return true; // for typed constant we done,
- // but the object ref field requires the normal constant treatment with unboxing of the ValueType or the cast
- exprType = constType = ((ConstantExpression)constValue).Value.GetType();
- }
-#endif
- if (constType.IsValueType)
- il.Demit(OpCodes.Unbox_Any, constType);
-#if NETFRAMEWORK
- else
- // 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
- }
- }
- else
- {
- if (constValue is string s)
- {
- il.Demit(s, OpCodes.Ldstr);
- return true;
- }
- if (constValue is Type t)
- {
- il.Demit(OpCodes.Ldtoken, t);
- return EmitMethodCall(il, GetTypeFromHandleMethod);
- }
- if (!TryEmitPrimitiveOrEnumOrDecimalConstant(il, constValue, constType))
- return false;
- }
-
- if (exprType != constType && constType.IsValueType)
- {
- if (exprType.IsNullable())
- il.Demit(OpCodes.Newobj, exprType.GetNullableConstructor());
- else if (exprType == typeof(object))
- il.Demit(OpCodes.Box, constType); // using normal type for Enum instead of underlying type
- }
- return true;
- }
-
- /// Emit the IL for the value of the primitive type.
- public static bool TryEmitPrimitiveOrEnumOrDecimalConstant(ILGenerator il, object constValue, Type constType)
- {
- if (constType.IsEnum)
- constType = Enum.GetUnderlyingType(constType);
-
- switch (Type.GetTypeCode(constType))
- {
- case TypeCode.Boolean:
- il.Demit((bool)constValue ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); // todo: @perf check for LightExpression
- break;
- case TypeCode.Char:
- EmitLoadConstantInt(il, (char)constValue);
- break;
- case TypeCode.SByte:
- EmitLoadConstantInt(il, (sbyte)constValue);
- break;
- case TypeCode.Byte:
- EmitLoadConstantInt(il, (byte)constValue);
- break;
- case TypeCode.Int16:
- EmitLoadConstantInt(il, (short)constValue);
- break;
- case TypeCode.UInt16:
- EmitLoadConstantInt(il, (ushort)constValue);
- break;
- case TypeCode.Int32:
- EmitLoadConstantInt(il, (int)constValue);
- break;
- case TypeCode.UInt32:
- unchecked
- {
- EmitLoadConstantInt(il, (int)(uint)constValue);
- }
- break;
- case TypeCode.Int64:
- il.Demit(OpCodes.Ldc_I8, (long)constValue);
- break;
- case TypeCode.UInt64:
- unchecked
- {
- il.Demit(OpCodes.Ldc_I8, (long)(ulong)constValue);
- }
- break;
- case TypeCode.Double:
- il.Demit(OpCodes.Ldc_R8, (double)constValue);
- break;
- case TypeCode.Single:
- il.Demit(OpCodes.Ldc_R4, (float)constValue);
- break;
- case TypeCode.Decimal:
- EmitDecimalConstant((decimal)constValue, il);
- break;
- // todo: @feature for net7 add Half, Int128, UInt128
- default:
- if (constType == typeof(IntPtr))
- {
- il.Demit(OpCodes.Ldc_I8, ((IntPtr)constValue).ToInt64());
- break;
- }
- else if (constType == typeof(UIntPtr))
- {
- unchecked
- {
- il.Demit(OpCodes.Ldc_I8, (long)((UIntPtr)constValue).ToUInt64());
- }
- break;
- }
- return false;
- }
- return true;
- }
-
- // todo: @perf optimize using Type.TypeCode
- internal static bool TryEmitNumberOne(ILGenerator il, Type type)
- {
- if (type == typeof(int) || type == typeof(char) || type == typeof(short) ||
- type == typeof(byte) || type == typeof(ushort) || type == typeof(sbyte) ||
- type == typeof(uint))
- il.Demit(OpCodes.Ldc_I4_1);
- else if (type == typeof(long) || type == typeof(ulong) ||
- type == typeof(IntPtr) || type == typeof(UIntPtr))
- il.Demit(OpCodes.Ldc_I8, (long)1);
- else if (type == typeof(float))
- il.Demit(OpCodes.Ldc_R4, 1f);
- else if (type == typeof(double))
- il.Demit(OpCodes.Ldc_R8, 1d);
- else
- return false;
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static void EmitLoadClosureArrayItem(ILGenerator il, int i)
- {
- il.Demit(OpCodes.Ldloc_0);// SHOULD BE always at 0 location; load array field variable on the stack
- EmitLoadConstantInt(il, i);
- il.Demit(OpCodes.Ldelem_Ref);
- }
-
- internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, ref ClosureInfo closure)
- {
- // todo: @perf load the field to `var` only if the constants are more than 1
- // Load constants array field from Closure and store it into the variable
- il.Demit(OpCodes.Ldarg_0);
- il.Demit(OpCodes.Ldfld, ArrayClosureArrayField);
- EmitStoreLocalVariable(il, il.GetNextLocalVarIndex(typeof(object[]))); // always does Stloc_0, because it is done at start of the lambda emit
-
- // important that the constant will contain the nested lambdas as well in the same array after the actual constants,
- // so the Count indicates where the constants end
- var constItems = closure.Constants.Items;
- var constCount = closure.Constants.Count;
-
- short varIndex;
- for (var i = 0; i < constCount; i++)
- {
- ref var constUsage = ref closure.ConstantUsageThenVarIndex.GetSurePresentRef(i);
- if (constUsage > 1) // todo: @perf should we proceed to do this or simplify and remove the usages for the closure info?
- {
- EmitLoadClosureArrayItem(il, i);
- var constValue = constItems[i];
- var constType = constValue.GetType();
- if (constType.IsValueType)
- il.Demit(OpCodes.Unbox_Any, constType);
-
- varIndex = (short)il.GetNextLocalVarIndex(constType);
- constUsage = (short)(varIndex + 1); // to distinguish from the default 1
- EmitStoreLocalVariable(il, varIndex);
- }
- }
-
- var nestedLambdasCount = closure.NestedLambdas.Count;
- if (nestedLambdasCount != 0)
- {
- var nestedLambdas = closure.NestedLambdas.Items;
- for (var i = 0; i < nestedLambdasCount; i++)
- {
- var lambdaInfo = nestedLambdas[i];
- EmitLoadClosureArrayItem(il, constCount + i);
- lambdaInfo.LambdaVarIndex = varIndex = (short)il.GetNextLocalVarIndex(lambdaInfo.Lambda.GetType());
- EmitStoreLocalVariable(il, varIndex);
- }
- }
- }
-
- private static ConstructorInfo _decimalIntCtor, _decimalLongCtor;
- private static FieldInfo _decimalZero, _decimalOne;
-
- private static void EmitDecimalConstant(decimal value, ILGenerator il)
- {
- if (value == 0 | value == 1)
- {
- // emit Decimal.Zero or Decimal.One instead of new Decimal(0) or new Decimal(1)
- var field = value == 0 ?
- _decimalZero ?? (_decimalZero = typeof(Decimal).GetField(nameof(Decimal.Zero))) :
- _decimalOne ?? (_decimalOne = typeof(Decimal).GetField(nameof(Decimal.One)));
- il.Demit(OpCodes.Ldsfld, field);
- return;
- }
-
- //check if decimal has decimal places, if not use shorter IL code (constructor from int or long)
- if (value % 1 == 0)
- {
- if (value >= int.MinValue && value <= int.MaxValue)
- {
- EmitLoadConstantInt(il, decimal.ToInt32(value));
- il.Demit(OpCodes.Newobj, _decimalIntCtor ?? (_decimalIntCtor = typeof(decimal).FindSingleParamConstructor(typeof(int))));
- return;
- }
-
- if (value >= long.MinValue && value <= long.MaxValue)
- {
- il.Demit(OpCodes.Ldc_I8, decimal.ToInt64(value));
- il.Demit(OpCodes.Newobj, _decimalLongCtor ?? (_decimalLongCtor = typeof(decimal).FindSingleParamConstructor(typeof(long))));
- return;
- }
- }
-
- if (value == decimal.MinValue)
- {
- il.Demit(OpCodes.Ldsfld, typeof(decimal).GetField(nameof(decimal.MinValue)));
- return;
- }
-
- if (value == decimal.MaxValue)
- {
- il.Demit(OpCodes.Ldsfld, typeof(decimal).GetField(nameof(decimal.MaxValue)));
- return;
- }
-
- var parts = decimal.GetBits(value);
- var sign = (parts[3] & 0x80000000) != 0;
- var scale = (byte)((parts[3] >> 16) & 0x7F);
-
- EmitLoadConstantInt(il, parts[0]);
- EmitLoadConstantInt(il, parts[1]);
- EmitLoadConstantInt(il, parts[2]);
-
- il.Demit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- EmitLoadConstantInt(il, scale);
- il.Demit(OpCodes.Conv_U1);
- il.Demit(OpCodes.Newobj, _decimalCtor.Value);
- }
-
- private static readonly Lazy _decimalCtor = new Lazy(() =>
- {
- foreach (var ctor in typeof(decimal).GetTypeInfo().DeclaredConstructors)
- if (ctor.GetParameters().Length == 5)
- return ctor;
- return null;
- });
-
- [MethodImpl((MethodImplOptions)256)]
- private static int InitValueTypeVariable(ILGenerator il, Type exprType, int valueVarIndex)
- {
- Debug.Assert(valueVarIndex != -1);
- EmitLoadLocalVariableAddress(il, valueVarIndex);
- il.Demit(OpCodes.Initobj, exprType);
- return valueVarIndex;
- }
-
- // todo: @perf merge with EmitLoadLocalVariable
- private static int InitValueTypeVariable(ILGenerator il, Type exprType) =>
- InitValueTypeVariable(il, exprType, il.GetNextLocalVarIndex(exprType));
-
-#if LIGHT_EXPRESSION
- private static bool EmitNewArrayBounds(NewArrayExpression expr, IParameterProvider paramExprs,
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- var bounds = (IArgumentProvider)expr;
- var boundCount = bounds.ArgumentCount;
-#else
- private static bool EmitNewArrayBounds(NewArrayExpression expr, IReadOnlyList paramExprs,
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- var bounds = expr.Expressions;
- var boundCount = bounds.Count;
-#endif
- if (boundCount == 1)
- {
- if (!TryEmit(bounds.GetArgument(0), paramExprs, il, ref closure, setup, parent))
- return false;
- var elemType = expr.Type.GetElementType();
- if (elemType == null)
- return false;
- il.Demit(OpCodes.Newarr, elemType);
- }
- else
- {
- for (var i = 0; i < boundCount; i++)
- if (!TryEmit(bounds.GetArgument(i), paramExprs, il, ref closure, setup, parent))
- return false;
- il.Demit(OpCodes.Newobj, expr.Type.GetTypeInfo().DeclaredConstructors.GetFirst());
- }
- return true;
- }
-
-#if LIGHT_EXPRESSION
- private static bool EmitNewArrayInit(NewArrayExpression expr, IParameterProvider paramExprs,
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
-#else
- private static bool EmitNewArrayInit(NewArrayExpression expr, IReadOnlyList paramExprs,
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
-#endif
- var arrayType = expr.Type;
- if (arrayType.GetArrayRank() > 1)
- return false; // todo: @feature multi-dimensional array initializers are not supported yet, they also are not supported by the hoisted expression
-
- var elemType = arrayType.GetElementType();
- if (elemType == null)
- return false;
-
-#if LIGHT_EXPRESSION
- var elems = (IArgumentProvider)expr;
- var elemCount = elems.ArgumentCount;
-#else
- var elems = expr.Expressions;
- var elemCount = elems.Count;
-#endif
- EmitLoadConstantInt(il, elemCount); // emit the length of the array calculated from the number of initializer elements
- il.Demit(OpCodes.Newarr, elemType);
-
- var isElemOfValueType = elemType.IsValueType;
- for (var i = 0; i < elemCount; i++)
- {
- il.Demit(OpCodes.Dup);
- EmitLoadConstantInt(il, i);
- if (isElemOfValueType) // loading element address for later copying of value into it.
- {
- il.Demit(OpCodes.Ldelema, elemType);
- if (!TryEmit(elems.GetArgument(i), paramExprs, il, ref closure, setup, parent))
- return false;
- il.Demit(OpCodes.Stobj, elemType); // store element of value type by array element address
- }
- else
- {
- if (!TryEmit(elems.GetArgument(i), paramExprs, il, ref closure, setup, parent))
- return false;
- il.Demit(OpCodes.Stelem_Ref);
- }
- }
- return true;
- }
-
-#if LIGHT_EXPRESSION
- private static bool EmitMemberInit(MemberInitExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool EmitMemberInit(MemberInitExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- var valueVarIndex = -1;
- var exprType = expr.Type;
- if (exprType.IsValueType)
- valueVarIndex = il.GetNextLocalVarIndex(exprType);
-
- var newExpr = expr.NewExpression;
-#if LIGHT_EXPRESSION
- if (newExpr == null)
- {
- if (!TryEmit(expr.Expression, paramExprs, il, ref closure, setup, parent))
- return false;
- if (valueVarIndex != -1)
- EmitStoreLocalVariable(il, valueVarIndex);
- }
- else
-#endif
- {
-#if SUPPORTS_ARGUMENT_PROVIDER
- var argExprs = (IArgumentProvider)newExpr;
-#else
- var argExprs = newExpr.Arguments;
-#endif
- var argCount = argExprs.GetCount();
- if (argCount > 0)
- {
- var args = newExpr.Constructor.GetParameters();
- for (var i = 0; i < argCount; i++)
- if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, parent,
- args[i].ParameterType.IsByRef ? i : -1))
- return false;
- }
-
- if (newExpr.Constructor != null)
- {
- il.Demit(OpCodes.Newobj, newExpr.Constructor);
- if (valueVarIndex != -1)
- EmitStoreLocalVariable(il, valueVarIndex);
- }
- else if (valueVarIndex != -1)
- InitValueTypeVariable(il, exprType, valueVarIndex);
- else
- return false; // null constructor and not a value type, better to fallback
- }
-
-#if LIGHT_EXPRESSION
- var bindings = (IArgumentProvider)expr;
- var bindCount = bindings.ArgumentCount;
-#else
- var bindings = expr.Bindings;
- var bindCount = bindings.Count;
-#endif
- for (var i = 0; i < bindCount; i++)
- {
- var binding = bindings.GetArgument(i);
- if (binding.BindingType != MemberBindingType.Assignment) // todo: @feature is not supported yet
- return false;
-
- if (valueVarIndex != -1) // load local value address, to set its members
- EmitLoadLocalVariableAddress(il, valueVarIndex);
- else
- il.Demit(OpCodes.Dup); // duplicate member owner on stack
-
- if (!TryEmit(((MemberAssignment)binding).Expression, paramExprs, il, ref closure, setup, parent) ||
- !EmitMemberSet(il, binding.Member))
- return false;
- }
-
- if (valueVarIndex != -1)
- EmitLoadLocalVariable(il, valueVarIndex);
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static bool TryEmitPropertySet(ILGenerator il, PropertyInfo prop)
- {
- var method = prop.SetMethod;
- return method != null && EmitMethodCallOrVirtualCall(il, method);
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static bool EmitFieldSet(ILGenerator il, FieldInfo field)
- {
- il.Demit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field);
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static bool EmitMemberSet(ILGenerator il, MemberInfo member) =>
- member is PropertyInfo pr ? TryEmitPropertySet(il, pr) :
- member is FieldInfo field ? EmitFieldSet(il, field) :
- false;
-
-#if LIGHT_EXPRESSION
- private static bool TryEmitListInit(ListInitExpression expr, IParameterProvider paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#else
- private static bool TryEmitListInit(ListInitExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure,
- CompilerFlags setup, ParentFlags parent)
-#endif
- {
- var valueVarIndex = -1;
- var exprType = expr.Type;
- if (exprType.IsValueType)
- valueVarIndex = il.GetNextLocalVarIndex(exprType);
-
- var newExpr = expr.NewExpression;
-#if SUPPORTS_ARGUMENT_PROVIDER
- var argExprs = (IArgumentProvider)newExpr;
-#else
- var argExprs = newExpr.Arguments;
-#endif
- var argCount = argExprs.GetCount();
- if (argCount > 0)
- {
- var args = newExpr.Constructor.GetParameters();
- for (var i = 0; i < argCount; i++)
- if (!TryEmit(argExprs.GetArgument(i), paramExprs, il, ref closure, setup, parent,
- args[i].ParameterType.IsByRef ? i : -1))
- return false;
- }
-
- if (newExpr.Constructor != null)
- {
- il.Demit(OpCodes.Newobj, newExpr.Constructor);
- if (valueVarIndex != -1)
- EmitStoreLocalVariable(il, valueVarIndex);
- }
- else if (valueVarIndex != -1)
- InitValueTypeVariable(il, exprType, valueVarIndex);
- else
- return false; // null constructor and not a value type, better to fallback
-
- var inits = expr.Initializers;
- var initCount = inits.Count;
- var ok = true;
-
- // see the TryEmitMethodCall for the reason of the callFlags
- var callFlags = (parent
- & ~(ParentFlags.IgnoreResult | ParentFlags.MemberAccess | ParentFlags.InstanceAccess |
- ParentFlags.LambdaCall | ParentFlags.ReturnByRef))
- | ParentFlags.Call;
- for (var i = 0; i < initCount; ++i)
- {
- if (valueVarIndex != -1) // load local value address, to set its members
- EmitLoadLocalVariableAddress(il, valueVarIndex);
- else
- il.Demit(OpCodes.Dup); // duplicate member owner on stack
-
- var elemInit = inits.GetArgument(i);
- var method = elemInit.AddMethod;
- var methodParams = method.GetParameters();
-#if LIGHT_EXPRESSION
- var addArgs = (IArgumentProvider)elemInit;
- var addArgCount = elemInit.ArgumentCount;
-#else
- var addArgs = elemInit.Arguments;
- var addArgCount = addArgs.Count;
-#endif
- for (var a = 0; ok && a < addArgCount; ++a)
- ok = TryEmit(addArgs.GetArgument(a), paramExprs, il, ref closure, setup, callFlags, methodParams[a].ParameterType.IsByRef ? a : -1);
-
- if (!exprType.IsValueType)
- ok = EmitMethodCallOrVirtualCall(il, method);
- else if (!method.IsVirtual // #251 - no need for constrained or virtual call because it is already by-ref
- || method.DeclaringType == exprType)
- ok = EmitMethodCall(il, method);
- else
- {
- il.Demit(OpCodes.Constrained, exprType); // todo: @clarify it is a value type so... can we de-virtualize the call?
- ok = EmitVirtualMethodCall(il, method);
- }
- }
-
- if (valueVarIndex != -1)
- EmitLoadLocalVariable(il, valueVarIndex);
- return ok;
- }
-
- // the `right = null` argument indicates the increment/decrement operation
- private static bool TryEmitArithmeticAndOrAssign(
- Expression left, Expression right, Type exprType, ExpressionType nodeType, bool isPost,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
- {
- // we need the result variable and the time of Post/Pre when to store it only if the result is not ignored, otherwise don't bother
- var resultVar = -1;
- if (!parent.IgnoresResult())
- resultVar = il.GetNextLocalVarIndex(exprType);
-
- switch (left.NodeType)
- {
- case ExpressionType.Parameter:
- if (!isPost)
- return TryEmitAssignToParameterOrVariable((ParameterExpression)left, right,
- nodeType, isPost, exprType, paramExprs, il, ref closure, setup, parent, resultVar);
-
- // todo: @better split for now between the Increment/Decrement and the rest
- var p = (ParameterExpression)left;
-#if LIGHT_EXPRESSION
- var paramExprCount = paramExprs.ParameterCount;
-#else
- var paramExprCount = paramExprs.Count;
-#endif
- var paramIndex = -1;
- var localVarIndex = closure.GetDefinedLocalVarOrDefault(p);
- if (localVarIndex != -1)
- EmitLoadLocalVariable(il, localVarIndex);
- else
- {
- paramIndex = paramExprCount - 1;
- while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), p)) --paramIndex;
- if (paramIndex == -1)
- return false;
- if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0)
- ++paramIndex;
- EmitLoadArg(il, paramIndex);
- if (p.IsByRef)
- EmitLoadIndirectlyByRef(il, p.Type);
- }
-
- if (resultVar != -1 & isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use
-
- EmitIncOrDec(il, nodeType == ExpressionType.Add);
-
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
-
- if (localVarIndex != -1)
- EmitStoreLocalVariable(il, localVarIndex); // store incremented value into the local value;
- else if (p.IsByRef)
- {
- var incrementedVar = il.GetNextLocalVarIndex(exprType);
- EmitStoreLocalVariable(il, incrementedVar);
- EmitLoadArg(il, paramIndex);
- EmitLoadLocalVariable(il, incrementedVar);
- EmitStoreIndirectlyByRef(il, exprType);
- }
- else
- il.Demit(OpCodes.Starg_S, paramIndex);
- break;
-
- case ExpressionType.ArrayIndex:
- throw new InvalidOperationException("ArrayIndex is not supported for the left part of the assignment operation. Use Index instead.");
-
- case ExpressionType.MemberAccess:
- case ExpressionType.Index:
- var leftMemberExpr = left as MemberExpression;
- var orLeftIndexExpr = left as IndexExpression;
-
- // return early for not supported types of left value to avoid multiple checks below
- if (leftMemberExpr == null & orLeftIndexExpr == null)
- return false;
-
- var indexArgCount = -1;
-#if SUPPORTS_ARGUMENT_PROVIDER
- IArgumentProvider indexArgs = null;
-#else
- IReadOnlyList indexArgs = null;
-#endif
- if (orLeftIndexExpr != null)
- {
-#if SUPPORTS_ARGUMENT_PROVIDER
- indexArgs = (IArgumentProvider)orLeftIndexExpr;
-#else
- indexArgs = orLeftIndexExpr.Arguments;
-#endif
- indexArgCount = indexArgs.GetCount();
- if (indexArgCount > 4)
- return false; // todo: @feature more than 4 index arguments are not supported, and probably not need to be supported
- }
-
- var objExpr = leftMemberExpr != null ? leftMemberExpr.Expression : orLeftIndexExpr.Object;
-
- // Remove the InstanceCall because we need to operate on the (nullable) field value and not on `ref` to return the value.
- // We may avoid it in case of not returning the value or PreIncrement/PreDecrement, but let's do less checks and branching.
- var baseFlags = parent &
- ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall |
- ParentFlags.LambdaCall | ParentFlags.ReturnByRef);
- var rightOnlyFlags = baseFlags | ParentFlags.AssignmentRightValue;
-
-
- var memberOrIndexFlags = leftMemberExpr != null ? ParentFlags.MemberAccess : ParentFlags.IndexAccess;
- var leftArLeastFlags = baseFlags | ParentFlags.AssignmentLeftValue | memberOrIndexFlags;
-
- var leftIsByAddress = false;
- if (nodeType == ExpressionType.Assign)
- {
- Debug.Assert(right != null);
- var rightType = right.Type;
-
- // if the right part is the block or alike, it is better from the complexity perspective
- // to emit it first and then restore the assignment target from var and assign the value
- var rightVar = -1;
- if (right.NodeType.IsBlockLikeOrConditional() ||
- right.NodeType == ExpressionType.Invoke)
- {
- if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags))
- return false;
- if (closure.LastEmitIsAddress)
- EmitLoadIndirectlyByRef(il, rightType);
- rightVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(rightType);
- EmitStoreLocalVariable(il, rightVar);
- }
-
- // Emit the left-value instance and index(es) (for the index access)
- if (leftMemberExpr != null)
- {
- if (objExpr != null &&
- !TryEmit(objExpr, paramExprs, il, ref closure, setup, leftArLeastFlags | ParentFlags.InstanceAccess))
- return false;
- }
- else
- {
- Debug.Assert(orLeftIndexExpr != null);
- if (objExpr != null)
- {
- var isIndexerAMethodCall = indexArgCount > 1 | orLeftIndexExpr.Indexer != null;
- var objFlags = isIndexerAMethodCall ? ParentFlags.InstanceCall : ParentFlags.InstanceAccess | ParentFlags.IndexAccess;
- if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, objFlags | ParentFlags.AssignmentLeftValue))
- return false;
- }
- for (var i = 0; i < indexArgCount; i++)
- if (!TryEmit(indexArgs.GetArgument(i), paramExprs, il, ref closure, setup, baseFlags, -1))
- return false;
- }
-
- // Load already emitted or emit the right-value normally after the left to be assigned
- if (rightVar != -1)
- {
- EmitLoadLocalVariable(il, rightVar);
- }
- else
- {
- if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags))
- return false;
- if (resultVar != -1)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- }
-
- if (leftMemberExpr != null)
- {
- if (!EmitMemberSet(il, leftMemberExpr.Member))
- return false;
- }
- else // if (leftIndexExpr != null)
- {
- var ok = orLeftIndexExpr.Indexer != null
- ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.SetMethod)
- : indexArgCount == 1
- ? TryEmitArrayIndexSet(il, orLeftIndexExpr.Type) // one-dimensional array
- : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Set")); // multi-dimensional array
- if (!ok)
- return false;
- }
-
- if (resultVar != -1)
- EmitLoadLocalVariable(il, resultVar);
- return true;
- }
-
- // Here we are at Arithmetic + Assign:
- // 1. First loading the left part as a part of right assignment,
- // 2. Loading the right value
- // 3. Do arithmetic operation
- // 4. Storing the result in the local variable
- // 5. Loading the left value for assignment
- // 6. Loading the stored arithmetic result
- // 7. Assign the result
- var leftOrRightNullableAreNullLabel = default(Label);
- var leftType = left.Type;
- if (leftMemberExpr != null)
- {
- if (!TryEmitMemberGet(leftMemberExpr, paramExprs, il, ref closure, setup,
- leftArLeastFlags | ParentFlags.Arithmetic | ParentFlags.DupIt))
- return false;
- }
- else // if (leftIndexExpr != null)
- {
- // determine is the index essentially the method call to get/set value
- var isIndexerAMethodCall = indexArgCount > 1 | orLeftIndexExpr.Indexer != null;
- var objVar = -1;
- var objVarByAddress = false;
- if (objExpr != null)
- {
- var objFlags = isIndexerAMethodCall ? ParentFlags.InstanceCall : ParentFlags.InstanceAccess | ParentFlags.IndexAccess;
- if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, objFlags | ParentFlags.Arithmetic))
- return false;
-
- // required for calling the method on the value type parameter
- var objType = objExpr.Type;
- objVarByAddress = !closure.LastEmitIsAddress && objType.IsValueType && // todo: @better avoid ad-hocking with parameter here
- (objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef);
- if (objVarByAddress)
- objVar = EmitStoreAndLoadLocalVariableAddress(il, objType);
- else
- {
- if (objExpr is ParameterExpression pe && pe.IsByRef)
- objType = objType.MakeByRefType();
- objVar = EmitStoreAndLoadLocalVariable(il, objType);
- }
- }
-
- int indexArgVar0 = -1, indexArgVar1 = -1, indexArgVar2 = -1, indexArgVar3 = -1; // using stackalloc array?
- for (var i = 0; i < indexArgCount; i++)
- {
- var indexArg = indexArgs.GetArgument(i);
- if (!TryEmit(indexArg, paramExprs, il, ref closure, setup, baseFlags))
- return false;
- var indexArgVar = EmitStoreAndLoadLocalVariable(il, indexArg.Type);
- if (i == 0) indexArgVar0 = indexArgVar;
- else if (i == 1) indexArgVar1 = indexArgVar;
- else if (i == 2) indexArgVar2 = indexArgVar;
- else if (i == 3) indexArgVar3 = indexArgVar;
- }
-
- // repeat the load of the obj and index variables for the assignment here to avoid store and load of the right value
- if (objExpr != null)
- {
- if (!objVarByAddress)
- EmitLoadLocalVariable(il, objVar);
- else
- EmitLoadLocalVariableAddress(il, objVar);
- }
-
- EmitLoadLocalVariable(il, indexArgVar0); // there is always at least one index argument
- if (indexArgVar1 != -1)
- {
- EmitLoadLocalVariable(il, indexArgVar1);
- if (indexArgVar2 != -1)
- EmitLoadLocalVariable(il, indexArgVar2);
- if (indexArgVar3 != -1)
- EmitLoadLocalVariable(il, indexArgVar3);
- }
-
- var ok = !isIndexerAMethodCall
- ? TryEmitArrayIndexGet(il, orLeftIndexExpr.Type, ref closure, baseFlags) // one-dimensional array
- : orLeftIndexExpr.Indexer != null
- ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.GetMethod)
- : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array
- if (!ok)
- return false;
- }
-
- if (leftIsByAddress = closure.LastEmitIsAddress)
- EmitLoadIndirectlyByRef(il, leftType); // if the field is loaded by ref, it need to be loaded from the ref in order to do arithmetic operation on it
-
- var leftIsNullable = leftType.IsNullable();
- if (!leftIsNullable)
- {
- if (right == null) // optimization for the common increment/decrement case, indicated by using the null for the right argument
- {
- if (resultVar != -1 & isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value before doing any operation on it
- EmitIncOrDec(il, nodeType == ExpressionType.Add);
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- }
- else
- {
- var rightType = right.Type;
- if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags))
- return false;
-
- var rightIsNullable = rightType.IsNullable();
- if (rightIsNullable) // todo: @perf @clarify is it even possible to have left non-nullable and right nullable
- {
- var rightVar = EmitStoreAndLoadLocalVariableAddress(il, rightType);
- il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod());
-
- il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel());
-
- EmitLoadLocalVariableAddress(il, rightVar);
- il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap right operand
- }
-
- if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il))
- return false;
- if (resultVar != -1)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- }
- }
- else // if `leftIsNullable == true`
- {
- // Reuse the result variable for the field,
- // so it may be returned as the original value of field if nullable is `null` and we jump to the return
- var leftNullableVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(leftType);
- EmitStoreLocalVariable(il, leftNullableVar);
-
- if (right == null)
- {
- EmitLoadLocalVariableAddress(il, leftNullableVar);
- il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod());
-
- il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel());
-
- EmitLoadLocalVariableAddress(il, leftNullableVar);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
-
- EmitIncOrDec(il, nodeType == ExpressionType.Add);
- }
- else
- {
- // emit the right expression immediately after the left and then just process their results
- var rightType = right.Type;
- if (!TryEmit(right, paramExprs, il, ref closure, setup, rightOnlyFlags))
- return false;
- if (closure.LastEmitIsAddress)
- EmitLoadIndirectlyByRef(il, rightType);
-
- var rightVar = EmitStoreLocalVariable(il, rightType);
-
- var rightIsNullable = rightType.IsNullable();
- if (!rightIsNullable) // todo: @perf @clarify if it is possible to have left nullable and right non-nullable
- {
- EmitLoadLocalVariableAddress(il, leftNullableVar);
- il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod());
-
- il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel());
-
- EmitLoadLocalVariableAddress(il, leftNullableVar);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
-
- EmitLoadLocalVariable(il, rightVar);
- }
- else
- {
- EmitLoadLocalVariableAddress(il, leftNullableVar);
- il.Demit(OpCodes.Call, leftType.GetNullableHasValueGetterMethod());
-
- EmitLoadLocalVariableAddress(il, rightVar);
- il.Demit(OpCodes.Call, rightType.GetNullableHasValueGetterMethod());
- il.Demit(OpCodes.And);
- il.Demit(OpCodes.Brfalse, leftOrRightNullableAreNullLabel = il.DefineLabel());
-
- EmitLoadLocalVariableAddress(il, leftNullableVar);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap left operand
-
- EmitLoadLocalVariableAddress(il, rightVar);
- il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod()); // unwrap right operand
- }
-
- if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il))
- return false;
- }
-
- il.Demit(OpCodes.Newobj, leftType.GetNullableConstructor()); // wrap the result back into the nullable
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- }
-
- if (leftIsByAddress)
- EmitStoreIndirectlyByRef(il, leftType);
- else if (leftMemberExpr != null)
- {
- if (!EmitMemberSet(il, leftMemberExpr.Member))
- return false;
- }
- else // if (leftIndexExpr != null)
- {
- var ok = orLeftIndexExpr.Indexer != null
- ? EmitMethodCallOrVirtualCallCheckForNull(il, orLeftIndexExpr.Indexer.SetMethod)
- : indexArgCount == 1
- ? TryEmitArrayIndexSet(il, orLeftIndexExpr.Type) // one-dimensional array
- : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Set")); // multi-dimensional array
- if (!ok)
- return false;
- }
-
- if (leftIsNullable)
- {
- // todo: @perf @simplify avoid the Dup and the Pop for this case via storing and loading local var same as in `TryEmitArithmetic`
- if (leftIsByAddress | objExpr != null)
- {
- var skipPopLeftDuppedInstance = il.DefineLabel();
- il.Demit(OpCodes.Br_S, skipPopLeftDuppedInstance);
- il.DmarkLabel(leftOrRightNullableAreNullLabel); // jump here if nullables are null after checking them with `!HasValue`
-
- il.Demit(OpCodes.Pop); // pop the dupped instance address or the field address, or the array instance address
-
- if (orLeftIndexExpr != null)
- {
- il.Demit(OpCodes.Pop); // pop the first index argument which is always present
- if (indexArgCount > 1)
- {
- il.Demit(OpCodes.Pop);
- if (indexArgCount > 2)
- {
- il.Demit(OpCodes.Pop);
- if (indexArgCount > 3)
- il.Demit(OpCodes.Pop); // pop the 4th last supported index argument
- }
- }
- }
-
- il.DmarkLabel(skipPopLeftDuppedInstance);
- }
- else
- {
- il.DmarkLabel(leftOrRightNullableAreNullLabel); // jump here if nullables are null after checking them with `!HasValue`
- }
- }
- break;
- default:
- return false;
- }
-
- if (resultVar != -1)
- EmitLoadLocalVariable(il, resultVar);
- return true;
- }
-
- private static ExpressionType AssignToArithmeticOrSelf(ExpressionType nodeType) => nodeType switch
- {
- ExpressionType.AddAssign => ExpressionType.Add,
- ExpressionType.AddAssignChecked => ExpressionType.AddChecked,
- ExpressionType.SubtractAssign => ExpressionType.Subtract,
- ExpressionType.SubtractAssignChecked => ExpressionType.SubtractChecked,
- ExpressionType.MultiplyAssign => ExpressionType.Multiply,
- ExpressionType.MultiplyAssignChecked => ExpressionType.MultiplyChecked,
- ExpressionType.DivideAssign => ExpressionType.Divide,
- ExpressionType.ModuloAssign => ExpressionType.Modulo,
- ExpressionType.PowerAssign => ExpressionType.Power,
- ExpressionType.AndAssign => ExpressionType.And,
- ExpressionType.OrAssign => ExpressionType.Or,
- ExpressionType.ExclusiveOrAssign => ExpressionType.ExclusiveOr,
- ExpressionType.LeftShiftAssign => ExpressionType.LeftShift,
- ExpressionType.RightShiftAssign => ExpressionType.RightShift,
- _ => nodeType
- };
-
- // the null `right` means the increment/decrement operation
- private static bool TryEmitAssignToParameterOrVariable(
- ParameterExpression left, Expression right, ExpressionType nodeType, bool isPost, Type exprType,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int resultVar = -1)
- {
-#if LIGHT_EXPRESSION
- var paramExprCount = paramExprs.ParameterCount;
-#else
- var paramExprCount = paramExprs.Count;
-#endif
- var ok = false;
- var flags = parent & ~ParentFlags.IgnoreResult;
-
- // First look if the left value is the local variable (in the current block) then store the right value in it.
- var leftLocalVar = closure.GetDefinedLocalVarOrDefault(left);
- if (leftLocalVar != -1)
- {
- if (resultVar != -1 & isPost)
- {
- EmitLoadLocalVariable(il, leftLocalVar);
- EmitStoreLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use
- }
-
- var isLeftByRef = left.IsByRef;
- if (nodeType == ExpressionType.Assign)
- {
- var varFlags = flags | ParentFlags.AssignmentRightValue;
- if (isLeftByRef)
- varFlags |= ParentFlags.AssignmentByRef;
- ok = TryEmit(right, paramExprs, il, ref closure, setup, varFlags);
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- if (!isLeftByRef)
- EmitStoreLocalVariable(il, leftLocalVar);
- }
- else
- {
- ok = TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags | ParentFlags.AssignmentLeftValue);
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- if (isLeftByRef)
- EmitStoreIndirectlyByRef(il, exprType);
- else
- EmitStoreLocalVariable(il, leftLocalVar);
- }
-
- // assigning the new value into the already closed variable - it enables the recursive nested lambda calls, see #353
- var nestedLambdasCount = closure.NestedLambdas.Count;
- for (var i = 0; i < nestedLambdasCount; ++i)
- EmitStoreAssignedLeftVarIntoClosureArray(il, closure.NestedLambdas.Items[i], left, leftLocalVar);
-
- if (resultVar != -1)
- EmitLoadLocalVariable(il, resultVar);
- return ok;
- }
-
- // If not the variable, then look if it is the passed parameter - yes it is bad but you can assign to the parameter
- var paramIndex = paramExprCount - 1;
- while (paramIndex != -1 && !ReferenceEquals(paramExprs.GetParameter(paramIndex), left)) --paramIndex;
- if (paramIndex != -1)
- {
- if ((closure.Status & ClosureStatus.ShouldBeStaticMethod) == 0)
- ++paramIndex; // shift parameter index by one, because the first one will be closure
-
- var isLeftByRef = left.IsByRef;
- if (isLeftByRef)
- EmitLoadArg(il, paramIndex);
-
- if (resultVar != -1 & isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar); // for the post increment/decrement save the non-incremented value for the later further use
-
- ok = nodeType == ExpressionType.Assign
- ? TryEmit(right, paramExprs, il, ref closure, setup, flags)
- : TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags);
-
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
-
- if (isLeftByRef)
- EmitStoreIndirectlyByRef(il, left.Type);
- else
- il.Demit(OpCodes.Starg_S, paramIndex);
-
- if (resultVar != -1)
- EmitLoadLocalVariable(il, resultVar);
- return ok;
- }
-
- // check that it is a captured parameter by closure
- var nonPassedParamIndex = closure.NonPassedParameters.TryGetIndex(left, default(RefEq));
- if (nonPassedParamIndex == -1)
- return false;
-
- if (nodeType == ExpressionType.Assign)
- {
- if (!TryEmit(right, paramExprs, il, ref closure, setup, flags))
- return false;
- if (right is ParameterExpression rp && rp.IsByRef)
- EmitLoadIndirectlyByRef(il, rp.Type);
-
- var rightVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(exprType);
- EmitStoreLocalVariable(il, rightVar);
-
- // load array field and param item index
- il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero
- il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
- EmitLoadConstantInt(il, nonPassedParamIndex);
-
- EmitLoadLocalVariable(il, rightVar);
-
- il.TryEmitBoxOf(exprType);
- il.Demit(OpCodes.Stelem_Ref); // put the variable into array
-
- // assigning the new value into the already closed variable - it enables the recursive nested lambda calls, see #353
- var nestedLambdasCount = closure.NestedLambdas.Count;
- for (var i = 0; i < nestedLambdasCount; ++i)
- EmitStoreAssignedLeftVarIntoClosureArray(il, closure.NestedLambdas.Items[i], left, rightVar);
-
- if (resultVar != -1)
- EmitLoadLocalVariable(il, rightVar);
- return true;
- }
-
- if (resultVar != -1 & isPost)
- {
- il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero
- il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
- EmitLoadConstantInt(il, nonPassedParamIndex);
- il.Demit(OpCodes.Ldelem_Ref); // load the variable from array
- if (exprType.IsValueType)
- il.Demit(OpCodes.Unbox_Any, exprType);
-#if NETFRAMEWORK
- else
- il.Demit(OpCodes.Castclass, exprType);
-#endif
- EmitStoreLocalVariable(il, resultVar);
- }
-
- // todo: @perf optimize for the increment/decrement case
- if (!TryEmitArithmetic(left, right, nodeType, exprType, paramExprs, il, ref closure, setup, flags))
- return false;
-
- var arithmeticResultVar = resultVar != -1 ? resultVar : il.GetNextLocalVarIndex(exprType);
- EmitStoreLocalVariable(il, arithmeticResultVar);
-
- il.Demit(OpCodes.Ldarg_0); // load closure as it is always an argument zero
- il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
- EmitLoadConstantInt(il, nonPassedParamIndex);
-
- EmitLoadLocalVariable(il, arithmeticResultVar);
-
- il.TryEmitBoxOf(exprType);
- il.Demit(OpCodes.Stelem_Ref); // put the variable into array
-
- if (resultVar != -1 & !isPost)
- EmitLoadLocalVariable(il, arithmeticResultVar);
- return true;
- }
-
- private static void EmitStoreAssignedLeftVarIntoClosureArray(ILGenerator il, NestedLambdaInfo nestedLambdaInfo, ParameterExpression assignedLeftVar, int assignedLeftVarIndex)
- {
- if (nestedLambdaInfo.NonPassedParamsVarIndex == 0)
- return;
- var nonPassedParIndex = nestedLambdaInfo.NonPassedParameters.TryGetIndex(assignedLeftVar, default(RefEq));
- if (nonPassedParIndex == -1)
- return;
- EmitLoadLocalVariable(il, nestedLambdaInfo.NonPassedParamsVarIndex);
- EmitLoadConstantInt(il, nonPassedParIndex);
- EmitLoadLocalVariable(il, assignedLeftVarIndex);
- il.TryEmitBoxOf(assignedLeftVar.Type);
- il.Demit(OpCodes.Stelem_Ref); // put the variable into non-passed parameters (variables) array
- }
-
- private static void EmitLoadIndirectlyByRef(ILGenerator il, Type type)
- {
- if (type.IsEnum)
- type = Enum.GetUnderlyingType(type);
- switch (Type.GetTypeCode(type))
- {
- case TypeCode.Boolean: il.Demit(OpCodes.Ldind_U1); break;
- case TypeCode.Char: il.Demit(OpCodes.Ldind_U1); break;
- case TypeCode.Byte: il.Demit(OpCodes.Ldind_U1); break;
- case TypeCode.SByte: il.Demit(OpCodes.Ldind_I1); break;
- case TypeCode.Int16: il.Demit(OpCodes.Ldind_I2); break;
- case TypeCode.Int32: il.Demit(OpCodes.Ldind_I4); break;
- case TypeCode.Int64: il.Demit(OpCodes.Ldind_I8); break;
- case TypeCode.Double: il.Demit(OpCodes.Ldind_R8); break;
- case TypeCode.Single: il.Demit(OpCodes.Ldind_R4); break;
- case TypeCode.UInt16: il.Demit(OpCodes.Ldind_U2); break;
- case TypeCode.UInt32: il.Demit(OpCodes.Ldind_U4); break;
- case TypeCode.UInt64: il.Demit(OpCodes.Ldobj, type); break;
- case TypeCode.String: il.Demit(OpCodes.Ldind_Ref); break;
- default:
- if (type == typeof(IntPtr) | type == typeof(UIntPtr))
- il.Demit(OpCodes.Ldind_I);
- else if (type.IsValueType)
- il.Demit(OpCodes.Ldobj, type);
- else
- il.Demit(OpCodes.Ldind_Ref);
- break;
- }
- }
-
- private static void EmitStoreIndirectlyByRef(ILGenerator il, Type type)
- {
- if (type.IsEnum)
- type = Enum.GetUnderlyingType(type);
- switch (Type.GetTypeCode(type))
- {
- case TypeCode.Boolean: il.Demit(OpCodes.Stind_I1); break;
- case TypeCode.Char: il.Demit(OpCodes.Stind_I1); break;
- case TypeCode.Byte: il.Demit(OpCodes.Stind_I1); break;
- case TypeCode.SByte: il.Demit(OpCodes.Stind_I1); break;
- case TypeCode.Int16: il.Demit(OpCodes.Stind_I2); break;
- case TypeCode.Int32: il.Demit(OpCodes.Stind_I4); break;
- case TypeCode.Int64: il.Demit(OpCodes.Stind_I8); break;
- case TypeCode.Double: il.Demit(OpCodes.Stind_R8); break;
- case TypeCode.Single: il.Demit(OpCodes.Stind_R4); break;
- case TypeCode.String: il.Demit(OpCodes.Stind_Ref); break;
- case TypeCode.UInt16: il.Demit(OpCodes.Stind_I2); break;
- case TypeCode.UInt32: il.Demit(OpCodes.Stind_I4); break;
- case TypeCode.UInt64: il.Demit(OpCodes.Stind_I8); break;
- default:
- if (type == typeof(IntPtr) || type == typeof(UIntPtr))
- il.Demit(OpCodes.Stind_I);
- else if (type.IsValueType)
- il.Demit(OpCodes.Stobj, type);
- else
- il.Demit(OpCodes.Stind_Ref);
- break;
- }
- }
-
- private static bool TryEmitArrayIndexGet(ILGenerator il, Type type, ref ClosureInfo closure, ParentFlags parent)
- {
- if (!type.IsValueType)
- {
- il.Demit(OpCodes.Ldelem_Ref);
- return true;
- }
-
- // access the value type by address when it is used later for the member access, as instance in the method call, assigned to the left;
- if ((parent & (ParentFlags.MemberAccess | ParentFlags.InstanceAccess | ParentFlags.AssignmentByRef)) != 0)
- {
- il.Demit(OpCodes.Ldelema, type);
- closure.LastEmitIsAddress = true;
- return true;
- }
- // todo: @perf @simplify convert to switch on TypeCode
- if (type == typeof(Int32))
- il.Demit(OpCodes.Ldelem_I4);
- else if (type == typeof(Int64))
- il.Demit(OpCodes.Ldelem_I8);
- else if (type == typeof(Int16))
- il.Demit(OpCodes.Ldelem_I2);
- else if (type == typeof(SByte))
- il.Demit(OpCodes.Ldelem_I1);
- else if (type == typeof(Single))
- il.Demit(OpCodes.Ldelem_R4);
- else if (type == typeof(Double))
- il.Demit(OpCodes.Ldelem_R8);
- else if (type == typeof(IntPtr))
- il.Demit(OpCodes.Ldelem_I);
- else if (type == typeof(UIntPtr))
- il.Demit(OpCodes.Ldelem_I);
- else if (type == typeof(Byte))
- il.Demit(OpCodes.Ldelem_U1);
- else if (type == typeof(UInt16))
- il.Demit(OpCodes.Ldelem_U2);
- else if (type == typeof(UInt32))
- il.Demit(OpCodes.Ldelem_U4);
- else
- il.Demit(OpCodes.Ldelem, type);
- return true;
- }
-
- private static bool TryEmitArrayIndexSet(ILGenerator il, Type elementType)
- {
- if (!elementType.IsValueType)
- {
- il.Demit(OpCodes.Stelem_Ref);
- return true;
- }
-
- if (elementType == typeof(Int32))
- il.Demit(OpCodes.Stelem_I4);
- else if (elementType == typeof(Int64))
- il.Demit(OpCodes.Stelem_I8);
- else if (elementType == typeof(Int16))
- il.Demit(OpCodes.Stelem_I2);
- else if (elementType == typeof(SByte))
- il.Demit(OpCodes.Stelem_I1);
- else if (elementType == typeof(Single))
- il.Demit(OpCodes.Stelem_R4);
- else if (elementType == typeof(Double))
- il.Demit(OpCodes.Stelem_R8);
- else if (elementType == typeof(IntPtr))
- il.Demit(OpCodes.Stelem_I);
- else if (elementType == typeof(UIntPtr))
- il.Demit(OpCodes.Stelem_I);
- else
- il.Demit(OpCodes.Stelem, elementType);
- return true;
- }
-
- private static bool TryEmitMethodCall(Expression expr,
-#if LIGHT_EXPRESSION
- IParameterProvider paramExprs,
-#else
- IReadOnlyList paramExprs,
-#endif
- ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex = -1)
- {
- var flags = ParentFlags.Call;
- var callExpr = (MethodCallExpression)expr;
- var objExpr = callExpr.Object;
- var method = callExpr.Method;
- var methodParams = method.GetParameters(); // todo: @perf @mem find how to avoid the call, look at `NewNoByRefArgs` expressions as example
-
- var objIsValueType = false;
- var loadObjByAddress = false;
- if (objExpr != null)
- {
- if (!TryEmit(objExpr, paramExprs, il, ref closure, setup, flags | ParentFlags.InstanceAccess))
- return false;
- objIsValueType = objExpr.Type.IsValueType;
- loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress;
- }
-
- var parCount = methodParams.Length;
- if (parCount == 0)
- {
- if (loadObjByAddress)
- EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
- }
- else
- {
-#if SUPPORTS_ARGUMENT_PROVIDER
- var callArgs = (IArgumentProvider)callExpr;
-#else
- var callArgs = callExpr.Arguments;
-#endif
- if (!closure.ArgsContainingComplexExpression.Map.ContainsKey(callExpr))
- {
- if (loadObjByAddress)
- EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
-
- for (var i = 0; i < parCount; i++)
- {
- var argExpr = callArgs.GetArgument(i);
- var parType = methodParams[i].ParameterType;
- if (!TryEmit(argExpr, paramExprs, il, ref closure, setup, flags, parType.IsByRef ? i : -1))
- return false;
- }
- }
- else
- {
- // don't forget to store the object into the variable first, before emitting the arguments
- var objVar = objExpr == null ? -1 : EmitStoreLocalVariable(il, objExpr.Type);
-
- SmallList, NoArrayPool