diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs
index 46712ff4..8519cca3 100644
--- a/src/FastExpressionCompiler/FastExpressionCompiler.cs
+++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs
@@ -1,12471 +1,12472 @@
-//
-/*
-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: @debug 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 ParamExprs = FastExpressionCompiler.LightExpression.IParameterProvider;
- using ArgExprs = FastExpressionCompiler.LightExpression.IArgumentProvider;
- using Bindings = FastExpressionCompiler.LightExpression.IArgumentProvider;
- 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 ParamExprs = System.Collections.Generic.IReadOnlyList;
- using ArgExprs = System.Collections.Generic.IReadOnlyList;
- using Bindings = System.Collections.Generic.IReadOnlyList;
- 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
- {
- /// 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, lambdaExpr.GetParamExprs(), 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.");
-
- var context = new CompilerContext(ClosureStatus.ShouldBeStaticMethod, lambdaExpr.GetParamExprs(), flags);
- var bodyExpr = lambdaExpr.Body;
- var nestedLambdas = new SmallList();
- if (!TryCollectBoundConstants(ref context, bodyExpr, null, ref nestedLambdas))
- return false;
-
- if ((context.Status & ClosureStatus.HasClosure) != 0)
- return false;
-
- var returnType = lambdaExpr.ReturnType;
- var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (!EmittingVisitor.TryEmit(bodyExpr, il, ref context, parent))
- return false;
-
- if (bodyExpr.Type.IsByRef && !returnType.IsByRef) // don't forget to de-ref the reference if the lambda should return de-ref value of the body and not itself by-ref return, see #414
- EmittingVisitor.EmitLoadIndirectlyByRef(il, bodyExpr.Type);
- 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), 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, lambdaExpr.GetParamExprs(), typeof(void), flags)
- ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys());
-
- /// 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, lambdaExpr.GetParamExprs(),
- 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 context = new CompilerContext(ClosureStatus.UserProvided | ClosureStatus.HasClosure, closureConstants, lambdaExpr.GetParamExprs(), flags);
- return TryCompileWithPreCreatedClosure(lambdaExpr, ref context, flags);
- }
-
- internal static TDelegate TryCompileWithPreCreatedClosure(
- LambdaExpression lambdaExpr, ref CompilerContext context, CompilerFlags flags) where TDelegate : class
- {
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.GetParamExprs());
-
- var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes,
- typeof(ExpressionCompiler), skipVisibility: true);
-
- var il = method.GetILGenerator();
- EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref context);
-
- var bodyExpr = lambdaExpr.Body;
- var returnType = lambdaExpr.ReturnType;
- var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (!EmittingVisitor.TryEmit(bodyExpr, il, ref context, parent))
- return null;
-
- if (bodyExpr.Type.IsByRef && !returnType.IsByRef) // don't forget to de-ref the reference if the lambda should return de-ref value of the body and not itself by-ref return, see #414
- EmittingVisitor.EmitLoadIndirectlyByRef(il, bodyExpr.Type);
- il.Demit(OpCodes.Ret);
-
- var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
- var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(context.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 context = new CompilerContext(ClosureStatus.UserProvided, lambdaExpr.GetParamExprs(), flags);
-
- var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(context.ParamExprs);
-
- var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, typeof(ArrayClosure),
- skipVisibility: true);
-
- var il = method.GetILGenerator();
-
- var bodyExpr = lambdaExpr.Body;
- var returnType = lambdaExpr.ReturnType;
- var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.Empty;
- if (!EmittingVisitor.TryEmit(bodyExpr, il, ref context, parent))
- return null;
-
- if (bodyExpr.Type.IsByRef && !returnType.IsByRef) // don't forget to de-ref the reference if the lambda should return de-ref value of the body and not itself by-ref return, see #414
- EmittingVisitor.EmitLoadIndirectlyByRef(il, bodyExpr.Type);
- 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;
- }
-
- internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, ParamExprs paramExprs,
- Type returnType, CompilerFlags flags)
- {
- var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs);
-#if LIGHT_EXPRESSION
- if (bodyExpr is NoArgsNewClassIntrinsicExpression newExpr)
- return CompileNoArgsNew(newExpr, delegateType, closureAndParamTypes, returnType, flags);
-#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 context = new CompilerContext(ClosureStatus.ToBeCollected, paramExprs, flags);
- var collectResult = TryCollectInfo(ref context, bodyExpr, null, ref context.NestedLambdas);
- if (collectResult == Result.OK)
- {
- var constantsAndNestedLambdas = (context.Status & ClosureStatus.HasClosure) != 0
- ? context.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 context);
-
- var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (returnType.IsByRef)
- parent |= ParentFlags.ReturnByRef;
-
- if (EmittingVisitor.TryEmit(bodyExpr, il, ref context, parent))
- {
- if (bodyExpr.Type.IsByRef && !returnType.IsByRef) // don't forget to de-ref the reference if the lambda should return de-ref value of the body and not itself by-ref return, see #414
- EmittingVisitor.EmitLoadIndirectlyByRef(il, bodyExpr.Type);
- 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?
-
- internal static Type[] RentPooledOrNewClosureTypeToParamTypes(ParamExprs paramExprs)
- {
- var count = paramExprs.GetParamCount();
- 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);
- 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 CompilerContext
- {
- /// 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;
-
- /// Track compiled lambda parameters
- public ParamExprs ParamExprs;
-
- /// Compiler flags
- public CompilerFlags CompilerFlags;
-
- /// Populates the info
- public CompilerContext(ClosureStatus status, ParamExprs paramExprs, CompilerFlags compilerFlags)
- {
- Status = status;
- Constants = new SmallList();
- LastEmitIsAddress = false;
- CurrentInlinedLambdaInvokeIndex = -1;
- ParamExprs = paramExprs;
- CompilerFlags = compilerFlags;
- }
-
- /// Populates info directly with provided closure object and constants.
- public CompilerContext(ClosureStatus status, object[] constValues, ParamExprs paramExprs, CompilerFlags compilerFlags)
- {
- Status = status;
-
- Constants = new SmallList(constValues ?? Tools.Empty());
- if (constValues != null)
- ConstantUsageThenVarIndex.InitCount(constValues.Length);
-
- LastEmitIsAddress = false;
- CurrentInlinedLambdaInvokeIndex = -1;
-
- ParamExprs = paramExprs;
- CompilerFlags = compilerFlags;
- }
-
- [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.GetLastSurePresentRef() >>> 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.RemoveLastSurePresent();
- }
- --_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.GetLastSurePresentRef() & 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);
- }
-
- /// 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 CompilerContext context, Expression expr,
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas)
- {
- var r = TryCollectInfo(ref context, expr, nestedLambda, ref rootNestedLambdas);
- return r == Result.OK || (context.CompilerFlags & 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 CompilerContext context, Expression expr,
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas)
- {
- var r = Result.OK;
- while (true)
- {
- if (expr == null)
- return Result.ExpressionIsNull;
-#if LIGHT_EXPRESSION
- if (expr.IsIntrinsic)
- return expr.TryCollectInfo(ref context, 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()))
- context.AddConstantOrIncrementUsageCount(value);
- return Result.OK;
-
- case ExpressionType.Parameter:
- {
- var paramExprs = context.ParamExprs;
- var paramCount = paramExprs.GetParamCount();
-
- // 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 && !context.IsLocalVar(parExpr))
- {
- if (nestedLambda == null) // means that we are in the root lambda
- return Result.ParameterIsNotVariableNorInPassedParameters;
- context.Status |= ClosureStatus.HasClosure;
- _ = nestedLambda.NonPassedParameters.GetIndexOrAdd(parExpr, default(RefEq));
- }
- return Result.OK;
- }
- case ExpressionType.Call:
- {
- var callExpr = (MethodCallExpression)expr;
- var callObjectExpr = callExpr.Object;
-
- var callArgs = callExpr.GetArgExprs();
- var argCount = callArgs.GetArgCount();
- if (argCount == 0)
- {
- if (callObjectExpr != null)
- {
- expr = callObjectExpr;
- continue;
- }
- return Result.OK;
- }
-
- if (callObjectExpr != null &&
- (r = TryCollectInfo(ref context, callObjectExpr, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- var hasComplexExpression = false;
- for (var i = 0; i < argCount; i++)
- {
- context.HasComplexExpression = false; // reset the flag because we want to know the real result after the arg collection
- if ((r = TryCollectInfo(ref context, callArgs.GetArgument(i), nestedLambda, ref rootNestedLambdas)) != 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 |= context.HasComplexExpression;
- }
-
- // propagate the value up the stack
- if (hasComplexExpression)
- {
- context.HasComplexExpression = true;
- context.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;
- var ctorArgs = newExpr.GetArgExprs();
- var argCount = ctorArgs.GetArgCount();
- if (argCount == 0)
- return r;
-
- var hasComplexExpression = false;
- for (var i = 0; i < argCount; i++)
- {
- context.HasComplexExpression = false;
- if ((r = TryCollectInfo(ref context, ctorArgs.GetArgument(i), nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- hasComplexExpression |= context.HasComplexExpression;
- }
-
- // pop the value up the stack
- if (hasComplexExpression)
- {
- context.HasComplexExpression = true;
- context.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 context, arrElems.GetArgument(i), nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- expr = arrElems.GetArgument(elemCount - 1);
- continue;
-
- case ExpressionType.MemberInit:
- return TryCollectMemberInitExprConstants(ref context, (MemberInitExpression)expr, nestedLambda, ref rootNestedLambdas);
-
- case ExpressionType.ListInit:
- return TryCollectListInitExprConstants(ref context, (ListInitExpression)expr, nestedLambda, ref rootNestedLambdas);
-
- 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;
- var nestedParamExprs = nestedLambdaExpr.GetParamExprs();
- context.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 context,
- nestedLambda, nestedParamExprs, ref compiledNestedLambda.NonPassedParameters))
- return Result.ParameterIsNotVariableNorInPassedParameters;
-
- return r;
- }
-
- var nestedLambdaContext = new CompilerContext(ClosureStatus.ToBeCollected, nestedParamExprs, context.CompilerFlags);
- var newNestedLambda = new NestedLambdaInfo(nestedLambdaExpr);
-
- if (nestedLambda != null)
- nestedLambda.NestedLambdas.Add(newNestedLambda);
- else
- rootNestedLambdas.Add(newNestedLambda);
-
- if ((r = TryCollectInfo(ref nestedLambdaContext, nestedLambdaExpr.Body, newNestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- if (newNestedLambda.NonPassedParameters.Count != 0 &&
- !PropagateNonPassedParamsToOuterLambda(ref context,
- nestedLambda, nestedParamExprs, ref newNestedLambda.NonPassedParameters))
- return Result.ParameterIsNotVariableNorInPassedParameters;
-
- if (!TryCompileNestedLambda(ref nestedLambdaContext, newNestedLambda))
- return Result.NestedLambdaCompileError;
-
- return r;
-
- case ExpressionType.Invoke:
- {
- var invokeExpr = (InvocationExpression)expr;
- var invokeArgs = invokeExpr.GetArgExprs();
- var invokeArgCount = invokeArgs.GetArgCount();
- var invokedExpr = invokeExpr.Expression;
- if ((context.CompilerFlags & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr)
- {
- var oldIndex = context.CurrentInlinedLambdaInvokeIndex;
- context.CurrentInlinedLambdaInvokeIndex = context.AddInlinedLambdaInvoke(invokeExpr);
- context.HasComplexExpression = false; // switch off because we have entered the inlined lambda
-
- ref var inlinedExpr = ref context.InlinedLambdaInvocation.Map.AddOrGetValueRef(invokeExpr, out var found);
- if (!found)
- inlinedExpr = CreateInlinedLambdaInvocationExpression(invokeArgs, lambdaExpr);
-
- if ((r = TryCollectInfo(ref context, inlinedExpr, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- context.HasComplexExpression = true;
- context.CurrentInlinedLambdaInvokeIndex = oldIndex;
- return r;
- }
-
- // No inlining, collect the normal way
- if (invokeArgCount == 0)
- {
- expr = invokedExpr;
- continue;
- }
-
- if ((r = TryCollectInfo(ref context, invokedExpr, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- var lastArgIndex = invokeArgCount - 1;
- for (var i = 0; i < lastArgIndex; i++)
- if ((r = TryCollectInfo(ref context, invokeArgs.GetArgument(i), nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- expr = invokeArgs.GetArgument(lastArgIndex);
- continue;
- }
- case ExpressionType.Conditional:
- var condExpr = (ConditionalExpression)expr;
- // Try structural branch elimination - skip collecting dead branch info
- {
- var reducedCond = Tools.TryReduceConditional(condExpr);
- if (!ReferenceEquals(reducedCond, condExpr))
- {
- expr = reducedCond;
- continue;
- }
- }
- if ((r = TryCollectInfo(ref context, condExpr.Test, nestedLambda, ref rootNestedLambdas)) != Result.OK ||
- (r = TryCollectInfo(ref context, condExpr.IfFalse, nestedLambda, ref rootNestedLambdas)) != 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 context, st0.Right, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- expr = st1.Left;
- continue;
- }
-
- if (varExprCount == 1)
- context.PushBlockWithVars(varExprs[0]);
- else if (varExprCount != 0)
- context.PushBlockWithVars(varExprs);
-
- for (var i = 0; i < blockExprCount - 1; i++)
- if ((r = TryCollectInfo(ref context, blockExprs[i], nestedLambda, ref rootNestedLambdas)) != 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 context, expr, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- context.PopBlock();
- return r;
-
- case ExpressionType.Loop:
- var loopExpr = (LoopExpression)expr;
- context.AddLabel(loopExpr.BreakLabel);
- context.AddLabel(loopExpr.ContinueLabel);
- expr = loopExpr.Body;
- continue;
-
- case ExpressionType.Index:
- var indexExpr = (IndexExpression)expr;
- var indexArgs = indexExpr.GetArgExprs();
- var indexArgCount = indexArgs.GetArgCount();
- for (var i = 0; i < indexArgCount; i++)
- if ((r = TryCollectInfo(ref context, indexArgs.GetArgument(i), nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- if (indexExpr.Object == null)
- return r;
- expr = indexExpr.Object;
- continue;
-
- case ExpressionType.Try:
- {
- context.HasComplexExpression = false;
- r = TryCollectTryExprInfo(ref context, (TryExpression)expr, nestedLambda, ref rootNestedLambdas);
- context.HasComplexExpression = true;
- return r;
- }
- case ExpressionType.Label:
- var labelExpr = (LabelExpression)expr;
- context.AddLabel(labelExpr.Target, context.CurrentInlinedLambdaInvokeIndex);
- if (labelExpr.Target != null)
- context.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)
- context.TargetToGotosAndLabels.Map.AddOrGetValueRef(gotoExpr.Target, out _).Item1++;
- if (gotoExpr.Value == null)
- return r;
- expr = gotoExpr.Value;
- continue;
-
- case ExpressionType.Switch:
- var switchExpr = ((SwitchExpression)expr);
- // Compile-time switch branch elimination (#489): if switch value is interpretable, collect only the matching branch
- if (Interpreter.TryFindSwitchBranch(switchExpr, context.CompilerFlags, out var switchMatchedBody))
- {
- if (switchMatchedBody != null)
- {
- expr = switchMatchedBody;
- continue;
- }
- return r; // no matched body and no default -> nothing to collect
- }
- if ((r = TryCollectInfo(ref context, switchExpr.SwitchValue, nestedLambda, ref rootNestedLambdas)) != Result.OK ||
- switchExpr.DefaultBody != null && // todo: @check is the order of collection affects the result?
- (r = TryCollectInfo(ref context, switchExpr.DefaultBody, nestedLambda, ref rootNestedLambdas)) != 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 context, switchCases[i].Body, nestedLambda, ref rootNestedLambdas)) != 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 context, binaryExpr.Left, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- expr = binaryExpr.Right;
- continue;
- }
-
- return Result.NotSupported_UnknownExpression;
- }
- }
- }
-
- private static Expression CreateInlinedLambdaInvocationExpression(ArgExprs invokeArgs, LambdaExpression lambdaExpr)
- {
- var invokeArgCount = invokeArgs.GetArgCount();
-
- // 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.
- var lambdaPars = lambdaExpr.GetParamExprs();
-
- 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;
- }
-
- private static bool PropagateNonPassedParamsToOuterLambda(ref CompilerContext context, NestedLambdaInfo lambda,
- ParamExprs nestedLambdaParamExprs, ref SmallList nestedNonPassedParams)
- {
- var paramExprs = context.ParamExprs;
- var paramExprCount = paramExprs.GetParamCount();
- var nestedLambdaParamExprCount = nestedLambdaParamExprs.GetParamCount();
-
- // 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 (context.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 CompilerContext nestedLambdaContext, NestedLambdaInfo nestedLambdaInfo)
- {
- // 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;
- var nestedLambdaParamExprs = nestedLambdaExpr.GetParamExprs();
-
-#if LIGHT_EXPRESSION
- if (nestedLambdaBody is NoArgsNewClassIntrinsicExpression newExpr)
- {
- var paramTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs);
- nestedLambdaInfo.Lambda = CompileNoArgsNew(newExpr, nestedLambdaExpr.Type, paramTypes, nestedReturnType, nestedLambdaContext.CompilerFlags);
- FreePooledClosureTypeAndParamTypes(paramTypes);
- return true;
- }
-#endif
- // copy the nested lambdas and non-passed parameters to closure info to read them in TryEmit
- nestedLambdaContext.NestedLambdas = nestedLambdaInfo.NestedLambdas;
- nestedLambdaContext.NonPassedParameters = nestedLambdaInfo.NonPassedParameters;
-
- var constantsAndNestedLambdas = (nestedLambdaContext.Status & ClosureStatus.HasClosure) != 0
- ? nestedLambdaContext.GetArrayOfConstantsAndNestedLambdas()
- : null;
-
- ArrayClosure nestedLambdaClosure = null;
- var hasDebugInfo = (nestedLambdaContext.CompilerFlags & 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 nestedLambdaContext);
-
- var parent = nestedReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall;
- if (nestedReturnType.IsByRef)
- parent |= ParentFlags.ReturnByRef;
-
- var emitOk = EmittingVisitor.TryEmit(nestedLambdaBody, il, ref nestedLambdaContext, 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;
-
- private static Result TryCollectMemberInitExprConstants(ref CompilerContext context, MemberInitExpression expr,
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas)
- {
- var newExpr = expr.NewExpression;
- var binds = expr.GetBindings();
- var r = Result.OK;
- if ((r = TryCollectInfo(ref context, newExpr, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- var count = binds.GetBindingCount();
- 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 context, ((MemberAssignment)b).Expression, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- }
- return r;
- }
-
- private static Result TryCollectListInitExprConstants(ref CompilerContext context, ListInitExpression expr,
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas)
- {
- var newExpr = expr.NewExpression;
- var inits = expr.Initializers;
- var count = inits.Count;
-
- var r = Result.OK;
- if ((r = TryCollectInfo(ref context, newExpr, nestedLambda, ref rootNestedLambdas)) != 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 context, args.GetArgument(a), nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- }
- return r;
- }
-
- private static Result TryCollectTryExprInfo(ref CompilerContext context, TryExpression tryExpr,
- NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas)
- {
- var r = Result.OK;
- if ((r = TryCollectInfo(ref context, tryExpr.Body, nestedLambda, ref rootNestedLambdas)) != 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)
- {
- context.PushBlockWithVars(catchExVar);
- if ((r = TryCollectInfo(ref context, catchExVar, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
- }
-
- if (catchBlock.Filter != null &&
- (r = TryCollectInfo(ref context, catchBlock.Filter, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- if ((r = TryCollectInfo(ref context, catchBlock.Body, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- if (catchExVar != null)
- context.PopBlock();
- }
-
- var faultOrFinally = tryExpr.Fault ?? tryExpr.Finally;
- if (faultOrFinally != null &&
- (r = TryCollectInfo(ref context, faultOrFinally, nestedLambda, ref rootNestedLambdas)) != Result.OK)
- return r;
-
- return r;
- }
-
- /// 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, ILGenerator il, ref CompilerContext context, ParentFlags parent, int byRefIndex = -1)
- {
- var exprType = expr.Type;
- while (true)
- {
- context.LastEmitIsAddress = false;
-#if LIGHT_EXPRESSION
- if (expr.IsIntrinsic)
- return expr.TryEmit(ref context, il, parent, byRefIndex);
-#endif
- var nodeType = expr.NodeType;
- switch (nodeType)
- {
- case ExpressionType.Parameter:
- return (parent & ParentFlags.IgnoreResult) != 0 ||
- TryEmitParameter((ParameterExpression)expr, il, ref context, 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, il, ref context, parent);
-
- case ExpressionType.TypeIs:
- case ExpressionType.TypeEqual:
- return TryEmitTypeIsOrEqual((TypeBinaryExpression)expr, il, ref context, parent);
-
- case ExpressionType.Convert:
- case ExpressionType.ConvertChecked:
- return TryEmitConvert((UnaryExpression)expr, il, ref context, parent);
-
- case ExpressionType.ArrayIndex:
- var arrIndexExpr = (BinaryExpression)expr;
- return TryEmit(arrIndexExpr.Left, il, ref context, parent | ParentFlags.IndexAccess)
- && TryEmit(arrIndexExpr.Right, il, ref context, parent | ParentFlags.IndexAccess) // #265
- && TryEmitArrayIndexGet(il, exprType, ref context, parent);
-
- case ExpressionType.ArrayLength:
- if (!TryEmit(((UnaryExpression)expr).Operand, il, ref context, 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 context, byRefIndex);
-
- case ExpressionType.Call:
- return TryEmitMethodCall(expr, il, ref context, parent, byRefIndex);
-
- case ExpressionType.MemberAccess:
- return TryEmitMemberGet((MemberExpression)expr, il, ref context, parent, byRefIndex);
-
- case ExpressionType.New:
- return TryEmitNew(expr, il, ref context, parent);
-
- case ExpressionType.NewArrayBounds:
- return EmitNewArrayBounds((NewArrayExpression)expr, il, ref context, parent);
-
- case ExpressionType.NewArrayInit:
- return EmitNewArrayInit((NewArrayExpression)expr, il, ref context, parent);
-
- case ExpressionType.MemberInit:
- return EmitMemberInit((MemberInitExpression)expr, il, ref context, parent);
-
- case ExpressionType.ListInit:
- return TryEmitListInit((ListInitExpression)expr, il, ref context, parent);
-
- case ExpressionType.Lambda:
- return TryEmitNestedLambda((LambdaExpression)expr, il, ref context);
-
- case ExpressionType.Invoke:
- return TryEmitInvoke((InvocationExpression)expr, il, ref context, 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, context.CompilerFlags))
- {
- 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, il, ref context, 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, context.CompilerFlags)
- || TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, il, ref context, 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, il, ref context, 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, context.CompilerFlags))
- {
- if ((parent & ParentFlags.IgnoreResult) == 0)
- il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- return true;
- }
- return TryEmitLogicalOperator((BinaryExpression)expr, nodeType, il, ref context, 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, context.CompilerFlags))
- {
- if ((parent & ParentFlags.IgnoreResult) == 0)
- il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- return true;
- }
- return TryEmitNot((UnaryExpression)expr, il, ref context, parent);
- }
- case ExpressionType.Coalesce:
- return TryEmitCoalesceOperator((BinaryExpression)expr, il, ref context, parent);
-
- case ExpressionType.Conditional:
- var condExpr = (ConditionalExpression)expr;
- var testExpr = condExpr.Test;
- if (Interpreter.TryInterpretBool(out var testIsTrue, testExpr, context.CompilerFlags))
- {
- expr = testIsTrue ? condExpr.IfTrue : condExpr.IfFalse;
- continue; // no recursion, just continue with the left or right side of condition
- }
- // Try structural branch elimination (e.g., null == Default(X) -> always true/false)
- {
- var reducedCond = Tools.TryReduceConditional(condExpr);
- if (!ReferenceEquals(reducedCond, condExpr))
- {
- expr = reducedCond;
- continue;
- }
- }
- return TryEmitConditional(testExpr, condExpr.IfTrue, condExpr.IfFalse, il, ref context, parent);
-
- case ExpressionType.PostIncrementAssign:
- case ExpressionType.PreIncrementAssign:
- return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Add,
- nodeType == ExpressionType.PostIncrementAssign, il, ref context, parent);
-
- case ExpressionType.PostDecrementAssign:
- case ExpressionType.PreDecrementAssign:
- return TryEmitArithmeticAndOrAssign(((UnaryExpression)expr).Operand, null, exprType, ExpressionType.Subtract,
- nodeType == ExpressionType.PostDecrementAssign, il, ref context, 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, il, ref context, 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, il, ref context, parent);
-
- if (blockVarCount != 0)
- context.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) = context.TargetToGotosAndLabels.Map.TryGetValueRef(label.Target, out var found);
- if (found & gotos <= labels)
- {
- if ((parent & ParentFlags.TryCatch) != 0)
- {
- if ((context.CompilerFlags & 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, il, ref context, parent & ~ParentFlags.IgnoreResult))
- return false;
-
- if ((parent & ParentFlags.InlinedLambdaInvoke) != 0)
- {
- ref var foundLabel = ref context.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(gt.Target, out var labelFound);
- if (!labelFound || foundLabel.InlinedLambdaInvokeIndex == -1)
- return false;
- EmitGotoToReturnLabel(ref context.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, il, ref context, parent | ParentFlags.IgnoreResult))
- return false;
- }
- }
-
- parent |= ParentFlags.BlockResult;
- if (blockVarCount == 0)
- continue; // OMG! no recursion, continue with the last expression
-
- if (!TryEmit(expr, il, ref context, parent))
- return false;
-
- context.PopBlock();
- return true;
- }
- case ExpressionType.Loop:
- return TryEmitLoop((LoopExpression)expr, il, ref context, parent);
-
- case ExpressionType.Try:
- return TryEmitTryCatchFinallyBlock((TryExpression)expr, il, ref context, parent | ParentFlags.TryCatch);
-
- case ExpressionType.Throw:
- {
- var ok = true;
- var throwOperand = ((UnaryExpression)expr).Operand;
- if (throwOperand != null)
- ok = TryEmit(throwOperand, il, ref context, 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, il, ref context, parent);
-
- case ExpressionType.Goto:
- return TryEmitGoto((GotoExpression)expr, il, ref context, parent);
-
- case ExpressionType.Label:
- return TryEmitLabel((LabelExpression)expr, il, ref context, parent);
-
- case ExpressionType.Switch:
- return TryEmitSwitch((SwitchExpression)expr, il, ref context, 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;
-
- }
- }
- }
-
- private static bool TryEmitNew(Expression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var flags = ParentFlags.CtorCall;
- var newExpr = (NewExpression)expr;
- var argExprs = newExpr.GetArgExprs();
- var argCount = argExprs.GetArgCount();
- 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), il, ref context, flags, pars[0].ParameterType.IsByRef ? 0 : -1))
- return false;
- }
- else
- {
- if (!context.ArgsContainingComplexExpression.Map.ContainsKey(newExpr))
- {
- for (var i = 0; i < argCount; ++i)
- if (!TryEmit(argExprs.GetArgument(i), il, ref context, 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, il, ref context, 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;
-
- context.LastEmitIsAddress =
- (parent & ParentFlags.InstanceAccess) != 0 & (parent & ParentFlags.IgnoreResult) == 0 && newType.IsValueType;
- if (context.LastEmitIsAddress)
- EmitStoreAndLoadLocalVariableAddress(il, newType);
-
- if (parent.IgnoresResult())
- il.Demit(OpCodes.Pop);
-
- return true;
- }
-
- private static bool TryEmitLoop(LoopExpression loopExpr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- // 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 context.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(loopExpr.ContinueLabel, out var foundLabel);
- if (!foundLabel)
- return false;
- var continueLabel = continueLabelInfo.GetOrDefineLabel(il);
- il.DmarkLabel(continueLabel);
- }
-
- if (!TryEmit(loopExpr.Body, il, ref context, 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 context.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, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var p = parent & ~ParentFlags.IgnoreResult | ParentFlags.IndexAccess;
-
- var objExpr = indexExpr.Object;
- if (objExpr != null &&
- !TryEmit(objExpr, il, ref context, p | ParentFlags.InstanceAccess))
- return false;
-
- var indexArgs = indexExpr.GetArgExprs();
- var indexArgCount = indexArgs.GetArgCount();
- // 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), il, ref context, argParent, -1))
- return false;
-
- var indexerProp = indexExpr.Indexer;
- return indexerProp != null
- ? EmitMethodCallOrVirtualCallCheckForNull(il, indexerProp.GetMethod)
- : indexArgCount == 1
- ? TryEmitArrayIndexGet(il, indexExpr.Type, ref context, parent) // one-dimensional array
- : EmitMethodCallOrVirtualCallCheckForNull(il, objExpr?.Type.FindMethod("Get")); // multi-dimensional array
- }
-
- private static bool TryEmitLabel(LabelExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- ref var labelInfo = ref context.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, il, ref context, 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);
- }
-
- private static bool TryEmitGoto(GotoExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- ref var labelInfo = ref context.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr.Target, out var labelFound);
- if (!labelFound)
- {
- if ((context.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, il, ref context, 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 context.LambdaInvokeStackLabels.GetSurePresentRef(invokeIndex), il, gotoValue, OpCodes.Br);
- }
- }
- else
- il.Demit(OpCodes.Ret);
- return true;
-
- default:
- return false;
- }
- }
-
- private static bool TryEmitCoalesceOperator(BinaryExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- 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, il, ref context, 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, il, ref context, 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, il, ref context, flags))
- return false;
-
- if (right.Type != exprType)
- il.TryEmitBoxOf(right.Type);
-
- if (left.Type == exprType)
- il.DmarkLabel(labelFalse);
- else
- {
- il.Demit(OpCodes.Br_S, labelDone); // safe: jumps over Castclass (5 bytes) = 5 bytes
- 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;
- }
- }
-
- private static bool TryEmitTryCatchFinallyBlock(TryExpression tryExpr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
-#if DEMIT
- Debug.WriteLine("try {");
-#endif
- il.BeginExceptionBlock();
-
- if (!TryEmit(tryExpr.Body, il, ref context, 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 ((context.CompilerFlags & 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 = context.GetDefinedLocalVarOrDefault(exVarExpr);
- if (exVarIndex == -1)
- exVarIndex = il.GetNextLocalVarIndex(exVarExpr.Type);
- context.PushBlockWithVars(exVarExpr, exVarIndex);
- EmitStoreLocalVariable(il, exVarIndex);
- }
-
- if (!TryEmit(catchBlock.Body, il, ref context, parent))
- return false;
-
- if (exVarExpr != null)
- context.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, il, ref context, 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, il, ref context, 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, ILGenerator il, ref CompilerContext context, ParentFlags parent, int byRefIndex = -1)
- {
- var paramExprs = context.ParamExprs;
- var paramExprCount = paramExprs.GetParamCount();
- 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 = context.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 = context.NestedLambdas.Count;
- if (nestedLambdasCount != 0)
- {
- var nestedLambdas = context.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 = context.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
- );
-
- context.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess);
-
- if (context.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 ((context.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
- );
-
- context.LastEmitIsAddress = !isParamOrVarByRef & (isPassedRef | valueTypeMemberButNotIndexAccess);
-
- if (context.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 = context.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 CompilerContext closure)
- {
- var paramExprCount = paramExprs.ParameterCount;
-#else
- public static bool TryEmitNonByRefNonValueTypeParameter(ParameterExpression paramExpr, IReadOnlyList paramExprs, ILGenerator il, ref CompilerContext 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,
- ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var exprType = expr.Type;
-
- if (!TryEmit(expr.Operand, il, ref context, 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_S, falseLabel); // safe: jumps over Ldc_I4_0 (1 byte) + Br_S (2 bytes) = 3 bytes
- il.Demit(OpCodes.Ldc_I4_0);
- il.Demit(OpCodes.Br_S, continueLabel); // safe: jumps over Ldc_I4_1 (1 byte) = 1 byte
- 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, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- if (!TryEmit(expr.Expression, il, ref context, 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 ((context.CompilerFlags & CompilerFlags.ThrowOnNotSupportedExpression) != 0)
- throw new NotSupportedExpressionException(Result.NotSupported_TypeEqual);
- return false;
- }
-
- private static bool TryEmitNot(UnaryExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var op = expr.Operand;
- if (op.NodeType == ExpressionType.Equal)
- return TryEmitComparison(((BinaryExpression)op).Left, ((BinaryExpression)op).Right,
- expr.Type, ExpressionType.NotEqual, il, ref context, parent);
-
- if (!TryEmit(op, il, ref context, 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, ILGenerator il, ref CompilerContext context, 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, il, ref context, parent & ~ParentFlags.InstanceAccess);
-
- // Emit the operand before going to the fast checks below
- if (!TryEmit(opExpr, il, ref context, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess))
- return false;
-
- if (sourceType == targetType)
- return il.EmitPopIfIgnoreResult(parent);
-
- if (targetType == underlyingNullableSourceType)
- {
- if (!context.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, il, ref context, 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), or a polymorphic base type
- {
- if (Nullable.GetUnderlyingType(methodParamType) == sourceType)
- il.Demit(OpCodes.Newobj, methodParamType.GetNullableConstructor());
- // else: methodParamType is a base type/interface of sourceType - the value on the stack is already assignment-compatible, no additional emit needed
- else Debug.Assert(methodParamType.IsAssignableFrom(sourceType),
- $"Expected the conversion operator parameter type `{methodParamType}` to be either Nullable<{sourceType}> or a base type of `{sourceType}`");
- }
-
- 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 CompilerContext context, 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(context.ContainsConstantsOrNestedLambdas(), exprType, constValue.GetType(), constValue, il, ref context, 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 CompilerContext context, 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 context, 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 CompilerContext 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 CompilerContext context)
- {
- // 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 = context.Constants.Items;
- var constCount = context.Constants.Count;
-
- short varIndex;
- for (var i = 0; i < constCount; i++)
- {
- ref var constUsage = ref context.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 = context.NestedLambdas.Count;
- if (nestedLambdasCount != 0)
- {
- var nestedLambdas = context.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));
-
- private static bool EmitNewArrayBounds(NewArrayExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var bounds = expr.GetArgExprs();
- var boundCount = bounds.GetArgCount();
- var flags = ParentFlags.CtorCall;
- for (var i = 0; i < boundCount; i++)
- if (!TryEmit(bounds.GetArgument(i), il, ref context, flags))
- return false;
- if (boundCount == 1)
- {
- var elemType = expr.Type.GetElementType();
- if (elemType == null)
- return false;
- il.Demit(OpCodes.Newarr, elemType);
- }
- else
- {
- il.Demit(OpCodes.Newobj, expr.Type.GetTypeInfo().DeclaredConstructors.GetFirst());
- }
- il.EmitPopIfIgnoreResult(parent);
- return true;
- }
-
- private static bool EmitNewArrayInit(NewArrayExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- 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;
-
- var elems = expr.GetArgExprs();
- var elemCount = elems.GetArgCount();
-
- 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), il, ref context, ParentFlags.Empty))
- return false;
- il.Demit(OpCodes.Stobj, elemType); // store element of value type by array element address
- }
- else
- {
- if (!TryEmit(elems.GetArgument(i), il, ref context, ParentFlags.Empty))
- return false;
- il.Demit(OpCodes.Stelem_Ref);
- }
- }
- il.EmitPopIfIgnoreResult(parent);
- return true;
- }
-
- private static bool EmitMemberInit(MemberInitExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- 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, il, ref context, parent))
- return false;
- if (valueVarIndex != -1)
- EmitStoreLocalVariable(il, valueVarIndex);
- }
- else
-#endif
- {
- var argExprs = newExpr.GetArgExprs();
- var argCount = argExprs.GetArgCount();
- if (argCount > 0)
- {
- var args = newExpr.Constructor.GetParameters();
- for (var i = 0; i < argCount; i++)
- if (!TryEmit(argExprs.GetArgument(i), il, ref context, 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 bindings = expr.GetBindings();
- var bindCount = bindings.GetBindingCount();
- 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, il, ref context, 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;
-
- private static bool TryEmitListInit(ListInitExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var valueVarIndex = -1;
- var exprType = expr.Type;
- if (exprType.IsValueType)
- valueVarIndex = il.GetNextLocalVarIndex(exprType);
-
- var newExpr = expr.NewExpression;
- var argExprs = newExpr.GetArgExprs();
- var argCount = argExprs.GetArgCount();
- if (argCount > 0)
- {
- var args = newExpr.Constructor.GetParameters();
- for (var i = 0; i < argCount; i++)
- if (!TryEmit(argExprs.GetArgument(i), il, ref context, 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();
- var addArgs = elemInit.GetArgExprs();
- var addArgCount = addArgs.GetArgCount();
-
- for (var a = 0; ok && a < addArgCount; ++a)
- ok = TryEmit(addArgs.GetArgument(a), il, ref context, 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,
- ILGenerator il, ref CompilerContext context, 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, il, ref context, parent, resultVar);
-
- // todo: @better split for now between the Increment/Decrement and the rest
- var p = (ParameterExpression)left;
- var paramExprs = context.ParamExprs;
- var paramExprCount = paramExprs.GetParamCount();
- var paramIndex = -1;
- var localVarIndex = context.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 ((context.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;
- ArgExprs indexArgs = null;
- if (orLeftIndexExpr != null)
- {
- indexArgs = orLeftIndexExpr.GetArgExprs();
- indexArgCount = indexArgs.GetArgCount();
- 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, il, ref context, rightOnlyFlags))
- return false;
- if (context.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, il, ref context, 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, il, ref context, objFlags | ParentFlags.AssignmentLeftValue))
- return false;
- }
- for (var i = 0; i < indexArgCount; i++)
- if (!TryEmit(indexArgs.GetArgument(i), il, ref context, 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, il, ref context, 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, il, ref context, 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, il, ref context, objFlags | ParentFlags.Arithmetic))
- return false;
-
- // required for calling the method on the value type parameter
- var objType = objExpr.Type;
- objVarByAddress = !context.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, il, ref context, 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 context, 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 = context.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, il, ref context, 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, il, ref context, rightOnlyFlags))
- return false;
- if (context.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,
- ILGenerator il, ref CompilerContext context, ParentFlags parent, int resultVar = -1)
- {
- var paramExprs = context.ParamExprs;
- var paramExprCount = paramExprs.GetParamCount();
-
- 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 = context.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, il, ref context, varFlags);
- if (resultVar != -1 & !isPost)
- EmitStoreAndLoadLocalVariable(il, resultVar);
- if (!isLeftByRef)
- EmitStoreLocalVariable(il, leftLocalVar);
- }
- else
- {
- ok = TryEmitArithmetic(left, right, nodeType, exprType, il, ref context, 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 = context.NestedLambdas.Count;
- for (var i = 0; i < nestedLambdasCount; ++i)
- EmitStoreAssignedLeftVarIntoClosureArray(il, context.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 ((context.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, il, ref context, flags)
- : TryEmitArithmetic(left, right, nodeType, exprType, il, ref context, 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 = context.NonPassedParameters.TryGetIndex(left, default(RefEq));
- if (nonPassedParamIndex == -1)
- return false;
-
- if (nodeType == ExpressionType.Assign)
- {
- if (!TryEmit(right, il, ref context, 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 = context.NestedLambdas.Count;
- for (var i = 0; i < nestedLambdasCount; ++i)
- EmitStoreAssignedLeftVarIntoClosureArray(il, context.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, il, ref context, 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
- }
-
- internal static void EmitLoadIndirectlyByRef(ILGenerator il, Type type)
- {
- if (type.IsByRef)
- type = type.GetElementType();
- 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 CompilerContext 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, ILGenerator il, ref CompilerContext context, 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, il, ref context, flags | ParentFlags.InstanceAccess))
- return false;
- objIsValueType = objExpr.Type.IsValueType;
- loadObjByAddress = objIsValueType && objExpr.NodeType != ExpressionType.Parameter && !context.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 (!context.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, il, ref context, 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> argVars = default;
- for (var i = 0; i < methodParams.Length; i++)
- {
- var argExpr = callArgs.GetArgument(i);
- var parType = methodParams[i].ParameterType;
- if (!TryEmit(argExpr, il, ref context, flags, parType.IsByRef ? i : -1))
- return false;
- argVars.Add(EmitStoreLocalVariable(il, parType));
- }
-
- // restore the object and the args from the variables in the proper order to emit the call
- if (objExpr != null)
- {
- if (loadObjByAddress)
- EmitLoadLocalVariableAddress(il, objVar);
- else
- EmitLoadLocalVariable(il, objVar);
- }
-
- for (var i = 0; i < methodParams.Length; i++)
- EmitLoadLocalVariable(il, argVars[i]);
- }
- }
-
- var ok = true;
- if (!objIsValueType)
- ok = EmitMethodCallOrVirtualCall(il, method);
- else if (method.DeclaringType != typeof(Enum) &&
- (!method.IsVirtual ||
- method.DeclaringType == objExpr.Type ||
- objExpr is ParameterExpression pe && pe.IsByRef))
- ok = EmitMethodCall(il, method);
- else
- {
- il.Demit(OpCodes.Constrained, objExpr.Type);
- ok = EmitVirtualMethodCall(il, method);
- }
-
- if (byRefIndex != -1)
- EmitStoreAndLoadLocalVariableAddress(il, method.ReturnType);
-
- if (parent.IgnoresResult() && method.ReturnType != typeof(void))
- il.Demit(OpCodes.Pop);
-
- context.LastEmitIsAddress = false;
- return ok;
- }
-
- public static bool TryEmitMemberGet(MemberExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent, int byRefIndex = -1)
- {
- var objExpr = expr.Expression;
- if (expr.Member is PropertyInfo prop)
- {
- if (objExpr != null)
- {
- var p = (parent | ParentFlags.InstanceCall)
- // removing ParentFlags.MemberAccess here because we are calling the method instead of accessing the field
- & ~(ParentFlags.IgnoreResult | ParentFlags.MemberAccess | ParentFlags.DupIt |
- ParentFlags.LambdaCall | ParentFlags.ReturnByRef);
-
- if (!TryEmit(objExpr, il, ref context, p))
- return false;
-
- if ((parent & ParentFlags.DupIt) != 0) // just duplicate the whatever is emitted for object
- il.Demit(OpCodes.Dup);
- else
- // Value type special treatment to load address of value instance in order to call a method.
- // For the parameters, we will skip the address loading because the `LastEmitIsAddress == true` for `Ldarga`,
- // so the condition here will be skipped
- if (!context.LastEmitIsAddress && objExpr.Type.IsValueType && !(objExpr is PE pe && pe.IsByRef))
- EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
- }
-
- context.LastEmitIsAddress = false;
- return EmitMethodCallOrVirtualCall(il, prop.GetMethod);
- }
-
- if (expr.Member is FieldInfo field)
- {
- if (objExpr != null)
- {
- var p = (parent | ParentFlags.InstanceAccess | ParentFlags.MemberAccess)
- & ~ParentFlags.IgnoreResult & ~ParentFlags.DupIt;
-
- if (!TryEmit(objExpr, il, ref context, p))
- return false;
-
- if (parent.IgnoresResult())
- {
- il.Demit(OpCodes.Pop); // pop the obj value - it is emitted only for the side effects
- return true;
- }
-
- // #248 indicates that expression is argument passed by ref to Call
- var isByAddress = byRefIndex != -1;
-
- // we are assigning to the field of ValueType so we need its address `val.Bar += 1`, #352
- if ((parent & ParentFlags.AssignmentLeftValue) != 0 && objExpr.Type.IsValueType)
- isByAddress = true;
- else
- // if the field is not used as an index, #302
- // or if the field is not accessed from the just constructed object `new Widget().DodgyValue`, #333
- if (((parent & ParentFlags.InstanceAccess) != 0 &
- (parent & ParentFlags.IndexAccess) == 0) && field.FieldType.IsValueType)
- isByAddress = true;
-
- // we don't need to duplicate the instance if we are working with the field address to save to it directly,
- // so the field address should be Dupped instead for loading and then storing by-ref for assignment (see below).
- // (don't forget to Pop if the assignment should be skipped for nullable with `null` value)
- if ((parent & ParentFlags.DupIt) != 0 &
- (!isByAddress | (parent & ParentFlags.AssignmentLeftValue) == 0))
- il.Demit(OpCodes.Dup);
-
- context.LastEmitIsAddress = isByAddress;
- if (!isByAddress)
- {
- if (objExpr.Type.IsEnum)
- EmitStoreAndLoadLocalVariableAddress(il, objExpr.Type);
- il.Demit(OpCodes.Ldfld, field);
- }
- else
- {
- il.Demit(OpCodes.Ldflda, field);
- if ((parent & ParentFlags.AssignmentLeftValue) != 0)
- il.Demit(OpCodes.Dup);
- }
- }
- else if (field.IsLiteral)
- {
- if (parent.IgnoresResult())
- return true; // do nothing
- var fieldValue = field.GetValue(null);
- if (fieldValue != null)
- return TryEmitConstant(false, null, field.FieldType, fieldValue, il, ref context);
- il.Demit(OpCodes.Ldnull);
- }
- else
- {
- if (parent.IgnoresResult())
- return true; // do nothing
- il.Demit(OpCodes.Ldsfld, field);
- }
- return true;
- }
- return false;
- }
-
- // ReSharper disable once FunctionComplexityOverflow
- private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, ILGenerator il, ref CompilerContext context)
- {
- var outerParamExprs = context.ParamExprs;
- var outerParamExprCount = outerParamExprs.GetParamCount();
-
- // First, find in closed compiled lambdas the one corresponding to the current lambda expression.
- // Situation with not found lambda is not possible/exceptional,
- // it means that we somehow skipped the lambda expression while collecting closure info.
- var outerNestedLambdasCount = context.NestedLambdas.Count;
- var outerNestedLambdas = context.NestedLambdas.Items;
- var i = outerNestedLambdasCount - 1;
- while (i != -1 && !outerNestedLambdas[i].HasTheSameLambdaExpression(lambdaExpr)) --i;
- if (i == -1)
- return false;
-
- ref var nestedLambdaInfo = ref outerNestedLambdas[i];
- EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex);
-
- // If lambda does not use any outer parameters to be set in closure, then we're done
- var nonPassedParams = nestedLambdaInfo.NonPassedParameters;
- if (nonPassedParams.Count == 0)
- return true;
-
- //-------------------------------------------------------------------
- // For the lambda with non-passed parameters (or variables) in closure
-
- // Emit the NonPassedParams array for the non-passed parameters and variables
- var nonPassedParamsCount = nonPassedParams.Count;
- EmitLoadConstantInt(il, nonPassedParamsCount); // load the length of array
- il.Demit(OpCodes.Newarr, typeof(object));
- var nonPassedParamsVarIndex = EmitStoreAndLoadLocalVariable(il, typeof(object[]));
- nestedLambdaInfo.NonPassedParamsVarIndex = (short)nonPassedParamsVarIndex;
-
- // Store the NonPassedParams back into the NestedLambda wrapper for the #437.
- // Also, it is needed to be able to assign the closed variable after the closure is passed to the lambda,
- // e.g. `var x = 1; var f = () => x + 1; x = 2; f();` expects 3, not 2
- il.Demit(OpCodes.Stfld, NestedLambdaForNonPassedParams.NonPassedParamsField);
-
- // Populate the NonPassedParams array
- for (var nestedParamIndex = 0; nestedParamIndex < nonPassedParamsCount; ++nestedParamIndex)
- {
- // todo: @wip move this code to where the assignment and by-ref parameter passing
- Debug.Assert(nestedParamIndex < 64, "Assume that we don't have more than 64 mutated non-passed parameters");
- if (nonPassedParamsCount < 64)
- nestedLambdaInfo.NonPassedParamMutatedIndexBits |= 1UL << nestedParamIndex;
-
- // Load the array and index where to store the item
- EmitLoadLocalVariable(il, nonPassedParamsVarIndex);
- EmitLoadConstantInt(il, nestedParamIndex);
-
- var nestedParam = nonPassedParams.GetSurePresentRef(nestedParamIndex);
- var outerParamIndex = outerParamExprCount - 1;
- while (outerParamIndex != -1 && !ReferenceEquals(outerParamExprs.GetParameter(outerParamIndex), nestedParam))
- --outerParamIndex;
- if (outerParamIndex != -1) // load parameter from input outer params
- {
- // Add `+1` to index because the `0` index is for the closure argument
- EmitLoadArg(il, outerParamIndex + 1);
- il.TryEmitBoxOf(nestedParam.Type);
- }
- else // load parameter from outer closure or from the local variables
- {
- var outerLocalVarIndex = context.GetDefinedLocalVarOrDefault(nestedParam);
- if (outerLocalVarIndex != -1) // it's a local variable
- {
- EmitLoadLocalVariable(il, outerLocalVarIndex);
- il.TryEmitBoxOf(nestedParam.Type);
- }
- else // it's a parameter from the outer closure
- {
- var outerNonPassedParamIndex = context.NonPassedParameters.TryGetIndex(nestedParam, default(RefEq));
- if (outerNonPassedParamIndex == -1)
- return false; // impossible, return error code 2 the same as in TryCollectInfo
-
- // Load the parameter from outer closure `Items` array
- il.Demit(OpCodes.Ldarg_0); // closure is always a first argument
- il.Demit(OpCodes.Ldfld, ArrayClosureWithNonPassedParamsField);
- EmitLoadConstantInt(il, outerNonPassedParamIndex);
- il.Demit(OpCodes.Ldelem_Ref);
- }
- }
-
- // Store the item into nested lambda array
- il.Demit(OpCodes.Stelem_Ref);
- }
-
- // Load the actual lambda delegate on stack
- EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex);
- il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParams.NestedLambdaField);
-
- // Load the nonPassedParams as a first argument of closure
- EmitLoadLocalVariable(il, nonPassedParamsVarIndex);
-
- // Load the constants as a second argument and call the closure constructor
- var lambda = nestedLambdaInfo.Lambda;
- if (lambda is NestedLambdaForNonPassedParamsWithConstants)
- {
- EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex);
- il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParamsWithConstants.ConstantsAndNestedLambdasField);
- il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsAndConstantsCtor);
- }
- else
- il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsCtor);
-
- // Call the `Curry` method with the nested lambda and closure to produce a closed lambda with the expected signature
- var lambdaType = (lambda is NestedLambdaForNonPassedParams lp ? lp.NestedLambda : lambda).GetType();
- var lambdaTypeArgs = lambdaType.GetGenericArguments();
- var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression;
- var closureMethod = nestedLambdaExpr.ReturnType == typeof(void)
- ? CurryClosureActions.Methods[lambdaTypeArgs.Length - 1].MakeGenericMethod(lambdaTypeArgs)
- : CurryClosureFuncs.Methods[lambdaTypeArgs.Length - 2].MakeGenericMethod(lambdaTypeArgs);
-
- var ok = EmitMethodCall(il, closureMethod);
-
- // Convert to the original possibly custom delegate type, see #308
- if (closureMethod.ReturnType != nestedLambdaExpr.Type)
- {
- il.Demit(OpCodes.Ldftn, closureMethod.ReturnType.FindDelegateInvokeMethod());
- il.Demit(OpCodes.Newobj, nestedLambdaExpr.Type.GetConstructors()[0]);
- }
-
- return ok;
- }
-
- private static bool TryEmitInvoke(InvocationExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var paramCount = context.ParamExprs.GetParamCount();
-
- var argExprs = expr.GetArgExprs();
- var argCount = argExprs.GetArgCount();
- var invokedExpr = expr.Expression;
- if ((context.CompilerFlags & CompilerFlags.NoInvocationLambdaInlining) == 0 && invokedExpr is LambdaExpression lambdaExpr)
- {
- parent |= ParentFlags.InlinedLambdaInvoke;
-
- ref var inlinedExpr = ref context.InlinedLambdaInvocation.Map.AddOrGetValueRef(expr, out var found);
- Debug.Assert(found, "The invocation expression should be collected in TryCollectInfo but it is not");
- if (!found)
- return false;
-
- if (!TryEmit(inlinedExpr, il, ref context, parent))
- return false;
-
- if ((parent & ParentFlags.IgnoreResult) == 0 && inlinedExpr.Type != typeof(void))
- {
- // find if the variable with the result is exist in the label infos
- ref var label = ref context.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(expr, out var labelFound);
- if (labelFound)
- {
- var returnVariableIndexPlusOne = label.ReturnVariableIndexPlusOneAndIsDefined >>> 1;
- if (returnVariableIndexPlusOne != 0)
- {
- il.DmarkLabel(label.ReturnLabel);
- EmitLoadLocalVariable(il, returnVariableIndexPlusOne - 1);
- }
- }
- }
- return true;
- }
-
- if (!TryEmit(invokedExpr, il, ref context, parent & ~ParentFlags.IgnoreResult)) // removing the IgnoreResult temporary because we need "full" lambda emit and we will re-apply the IgnoreResult later at the end of the method
- return false;
-
- //if (lambda is ConstantExpression lambdaConst) // todo: @perf opportunity to optimize
- // delegateInvokeMethod = ((Delegate)lambdaConst.Value).GetMethodInfo();
- //else
- var delegateInvokeMethod = invokedExpr.Type.FindDelegateInvokeMethod(); // todo: @perf slow thingy
- if (argCount != 0)
- {
- var useResult = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess;
- var args = delegateInvokeMethod.GetParameters(); // todo: @perf avoid this if possible
- for (var i = 0; i < args.Length; ++i)
- {
- var argExpr = argExprs.GetArgument(i);
- if (!TryEmit(argExpr, il, ref context, useResult, args[i].ParameterType.IsByRef ? i : -1))
- return false;
- }
- }
-
- EmitMethodCall(il, delegateInvokeMethod);
- if ((parent & ParentFlags.IgnoreResult) != 0 && delegateInvokeMethod.ReturnType != typeof(void))
- il.Demit(OpCodes.Pop);
-
- return true;
- }
-
- private static Label[][] _labelPool = null;
-
- private struct TestValueAndMultiTestCaseIndex
- {
- public long Value;
- public int CaseBodyIdx;
- public int MultiTestValCaseBodyIdxPlusOne; // 0 means not multi-test case, otherwise index+1
- }
-
- private static long ConvertValueObjectToLong(object valObj) =>
- Interpreter.ConvertValueObjectToLong(valObj);
-
- private static bool TryEmitSwitch(SwitchExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var switchValueExpr = expr.SwitchValue;
- var customEqualMethod = expr.Comparison;
- var cases = expr.Cases;
- var caseCount = cases.Count;
- var defaultBody = expr.DefaultBody;
-
- // Compile-time switch branch elimination (#489): if the switch value is interpretable, select the matching branch
- if (Interpreter.TryFindSwitchBranch(expr, context.CompilerFlags, out var matchedBody))
- return matchedBody == null || TryEmit(matchedBody, il, ref context, parent);
-
- // Optimization for the single case
- if (caseCount == 1 & defaultBody != null)
- {
- var cs0 = cases[0];
- if (cs0.TestValues.Count == 1)
- {
- Expression testExpr;
- if (customEqualMethod == null)
- {
- // todo: @perf avoid creation of the additional expression
- testExpr = Equal(switchValueExpr, cs0.TestValues[0]);
- if (Interpreter.TryInterpretBool(out var testResult, testExpr, context.CompilerFlags))
- return TryEmit(testResult ? cs0.Body : defaultBody, il, ref context, parent);
- }
- else
- testExpr = Call(customEqualMethod, switchValueExpr, cs0.TestValues[0]);
-
- return TryEmitConditional(testExpr, cs0.Body, defaultBody, il, ref context, parent);
- }
- }
-
- var switchValueType = switchValueExpr.Type;
- var switchValueParent = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess;
-
- // Check the pre-requisite conditions and emit OpCodes.Switch if possible, see #398
- const int minSwitchTableSize = 3;
- Debug.Assert(minSwitchTableSize >= 3); // The later code will assume at least so few of the cases count
- if (customEqualMethod == null && caseCount >= minSwitchTableSize && switchValueType.IsIntegerOrUnderlyingInteger())
- {
- var multipleTestValuesLabelsId = 0;
-#if TESTING || DEBUG
- SmallList, NoArrayPool> switchValues = default;
-#else
- SmallList, NoArrayPool> switchValues = default;
-#endif
- for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex)
- {
- var testValues = cases[caseIndex].TestValues;
- var testValueCount = testValues.Count;
- var id = testValueCount > 1 ? ++multipleTestValuesLabelsId : 0;
-
- for (var v = 0; v < testValueCount; ++v)
- {
- var testValExpr = testValues[v];
- if (testValExpr is not ConstantExpression && testValExpr is not DefaultExpression)
- {
- Debug.Assert(false, $"Not supported non-constant,non-default switch case value expression: `{testValExpr}`");
- return false;
- }
- var testValueLong = testValExpr is ConstantExpression constExpr ? ConvertValueObjectToLong(constExpr.Value) : 0L;
-
- // Adding a free slot for the new or for the shifted max value
- ref var freeValRef = ref switchValues.AddDefaultAndGetRef(); // the default value is (0,0)
- var lastIndex = switchValues.Count - 1;
- while (true)
- {
- --lastIndex;
- if (lastIndex == -1)
- {
- freeValRef.Value = testValueLong;
- freeValRef.CaseBodyIdx = caseIndex;
- freeValRef.MultiTestValCaseBodyIdxPlusOne = id;
- break;
- }
-
- ref var valRef = ref switchValues.GetSurePresentRef(lastIndex);
- Debug.Assert(valRef.Value != testValueLong, "Duplicate test value in switch case");
-
- // Shift current value further to get space for the smaller new value
- if (testValueLong < valRef.Value)
- {
- freeValRef = valRef; // shift the value by copying it into the free slot
- freeValRef = ref valRef; // set the free slot reference to the current value
- }
- else
- {
- lastIndex = 0; // stop searching
- }
- }
- }
- }
-
- // Let's analyze the switch values for the starting value and the gaps.
- // We require no more than nonContValueCountMax non-continuous values and no larger gap than the valueGapMin for the rest,
- // to consider the values for the switch table. Because otherwise the table will be filled with gaps and
- // will become too large for the final optimization goal.
- // Note that we cannot check the gaps earlier when collecting the values because the gaps may be filled at the end.
- const int valueGapMin = 4;
- const int nonContValueCountMax = 3;
- var contValueCount = 0;
- var valuesConditionsMet = true;
- var actualSwitchTableSize = 1; // start from the 1 and then add the gaps
-
- var firstTestValueIdx = 0;
- var switchValuesCount = switchValues.Count;
- var lastTestValueIdx = switchValuesCount - 1;
-
- // Check for the 1 or 2 outliers with the big gap only if there are 1/2 + minSwitchTableSize, otherwise the table is too small and just count the outlier toward the nonContValueCountMax gaps.
- if (switchValuesCount >= 1 + minSwitchTableSize)
- {
- if (switchValues.GetSurePresentRef(1).Value - switchValues.GetSurePresentRef(0).Value > valueGapMin)
- firstTestValueIdx = 1;
-
- if (firstTestValueIdx == 0 || switchValuesCount >= 2 + minSwitchTableSize) {
- if (switchValues.GetSurePresentRef(lastTestValueIdx).Value - switchValues.GetSurePresentRef(lastTestValueIdx - 1).Value > valueGapMin)
- --lastTestValueIdx;
- }
- }
- Debug.Assert(lastTestValueIdx - firstTestValueIdx + 1 >= minSwitchTableSize, "Postcondition: switch table between outliers should be of enough size");
- var prevSwitchValue = switchValues.GetSurePresentRef(firstTestValueIdx).Value;
- for (var i = firstTestValueIdx + 1; i <= lastTestValueIdx; ++i)
- {
- var currValue = switchValues.GetSurePresentRef(i).Value;
- var valueGap = currValue - prevSwitchValue;
- Debug.Assert(valueGap > 0, $"The values should be in increased order and non-equal but found {currValue} <= {prevSwitchValue}");
-
- if (valueGap > valueGapMin ||
- valueGap > 1 && ++contValueCount > nonContValueCountMax)
- {
- valuesConditionsMet = false;
- break;
- }
-
- prevSwitchValue = currValue;
- actualSwitchTableSize += (int)valueGap;
- }
-
- if (valuesConditionsMet)
- {
- Debug.Assert(actualSwitchTableSize >= lastTestValueIdx - firstTestValueIdx + 1, // take outliers into account
- $"The switch table size should be at least as large as the case count, but found {actualSwitchTableSize} < {lastTestValueIdx - firstTestValueIdx + 1}");
-
- var endOfSwitchLabel = il.DefineLabel();
- var defaultBodyLabel = defaultBody == null ? endOfSwitchLabel : il.DefineLabel();
-
-#if TESTING || DEBUG
- SmallList, NoArrayPool> sameCaseLabelIndexes = default;
-#else
- SmallList, NoArrayPool> sameCaseLabelIndexes = default;
-#endif
- sameCaseLabelIndexes.InitCount(multipleTestValuesLabelsId); // may stay empty if id is 0
-
-#if TESTING || DEBUG
- // Test the pool at work
- _labelPool ??= new Label[8][];
-#endif
- // Let define and collect the labels that we will be using for switch table and optional outliers
- ProvidedArrayPool> labelPool = default;
- labelPool.Init(_labelPool, 8);
- var switchTableLabels = labelPool.RentExactOrNew(actualSwitchTableSize);
-
- // Define outliers labels
- Label minOutlierLabel = default, maxOutlierLabel = default;
- int minOutlierMultiTestCaseBodyIdx = -1, maxOutlierMultiTestCaseBodyIdx = -1;
- int minOutlierCaseBodyIdx = -1, maxOutlierCaseBodyIdx = -1;
- if (firstTestValueIdx == 1)
- {
- ref var minOutlier = ref switchValues.GetSurePresentRef(0);
- minOutlierCaseBodyIdx = minOutlier.CaseBodyIdx;
- minOutlierMultiTestCaseBodyIdx = minOutlier.MultiTestValCaseBodyIdxPlusOne - 1;
- minOutlierLabel = il.DefineLabel(); // define the label that later can be shared with others in switch table or with max outlier
- }
- if (lastTestValueIdx == switchValuesCount - 2)
- {
- ref var maxOutlier = ref switchValues.GetSurePresentRef(switchValuesCount - 1);
- maxOutlierCaseBodyIdx = maxOutlier.CaseBodyIdx;
- maxOutlierMultiTestCaseBodyIdx = maxOutlier.MultiTestValCaseBodyIdxPlusOne - 1;
- maxOutlierLabel = maxOutlierMultiTestCaseBodyIdx != -1 && maxOutlierMultiTestCaseBodyIdx == minOutlierMultiTestCaseBodyIdx
- ? minOutlierLabel : il.DefineLabel(); // Check if the max outlier share the label with some and if this some is the min outlier
- }
-
- // Let's start with the continuous values from the start
- var firstTestValue = switchValues.GetSurePresentRef(firstTestValueIdx).Value;
- prevSwitchValue = firstTestValue - 1; // init the prev value to by 1 before the first for calc purposes
- var switchTableIndex = 0;
- for (var v = firstTestValueIdx; v <= lastTestValueIdx; ++v)
- {
- var currSwitchVal = switchValues.GetSurePresentRef(v);
- var currSwitchValue = currSwitchVal.Value;
- var valueGap = currSwitchValue - prevSwitchValue;
- if (valueGap > 1)
- {
- var endOfGap = switchTableIndex + valueGap - 1;
- while (switchTableIndex < endOfGap)
- switchTableLabels[switchTableIndex++] = defaultBodyLabel; // the label will be same as endOfSwitchLabel if no default body present
- }
-
- var caseIndex = currSwitchVal.MultiTestValCaseBodyIdxPlusOne - 1;
- if (caseIndex != -1)
- {
- Debug.Assert(caseIndex < sameCaseLabelIndexes.Count, "Invalid MultiTestValCaseBodyIdxPlusOne in switch case values");
- ref var labelIndexPlusOneRef = ref sameCaseLabelIndexes.GetSurePresentRef(caseIndex);
- if (labelIndexPlusOneRef > 0)
- {
- switchTableLabels[switchTableIndex] = switchTableLabels[labelIndexPlusOneRef - 1];
- }
- else if (caseIndex == minOutlierMultiTestCaseBodyIdx | caseIndex == maxOutlierMultiTestCaseBodyIdx)
- {
- switchTableLabels[switchTableIndex] = caseIndex == minOutlierMultiTestCaseBodyIdx ? minOutlierLabel : maxOutlierLabel;
- labelIndexPlusOneRef = switchTableIndex + 1;
- }
- else
- {
- switchTableLabels[switchTableIndex] = il.DefineLabel();
- labelIndexPlusOneRef = switchTableIndex + 1;
- }
- }
- else
- switchTableLabels[switchTableIndex] = il.DefineLabel();
-
- prevSwitchValue = currSwitchVal.Value;
- ++switchTableIndex;
- }
-
- // Emit the switch value here only after we sure about proceeding with switch table
- if (!TryEmit(switchValueExpr, il, ref context, switchValueParent, -1))
- return false;
-
- // Emit the min outlier before the switch table
- var switchValVar = -1;
- if (firstTestValueIdx == 1)
- {
- // store and use the switch value over in case of the outlier, otherwise just use whatever is on stack from the prev TryEmit
- EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType));
- EmitLoadConstantLong(il, switchValues.GetSurePresentRef(0).Value); // Load the min outlier
- il.Demit(OpCodes.Beq, minOutlierLabel);
- EmitLoadLocalVariable(il, switchValVar); // Load the switch var on stack for the next operation
- }
- else if (lastTestValueIdx == switchValuesCount - 2) // if the max outlier is defined, store and load the switch value
- {
- EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType));
- }
-
- // Before emitting switch we need to normalize the switch value to start from zero
- if (firstTestValue != 0)
- {
- EmitLoadConstantLong(il, firstTestValue);
- il.Demit(firstTestValue > 0 ? OpCodes.Sub : OpCodes.Add);
- }
-
- // Emit the switch instruction
- il.DemitSwitch(switchTableLabels);
-
- // Emit the max outlier after the switch table
- if (lastTestValueIdx == switchValuesCount - 2)
- {
- Debug.Assert(switchValVar != -1);
- EmitLoadLocalVariable(il, switchValVar);
- EmitLoadConstantLong(il, switchValues.GetSurePresentRef(switchValuesCount - 1).Value); // Load the max outlier
- il.Demit(OpCodes.Beq, maxOutlierLabel); // If switch value matches the outlier break to its label
- }
-
- // For the values OUTSIDE of switch table range, immediately branch to the default or to the end of the switch
- il.Demit(OpCodes.Br, defaultBodyLabel); // the label is the same as endOfSwitchLabel if no default body present
-
- for (var i = 0; i < caseCount; ++i)
- {
- var cs = cases[i];
- if (i == minOutlierCaseBodyIdx | i == maxOutlierCaseBodyIdx)
- il.DmarkLabel(i == minOutlierCaseBodyIdx ? minOutlierLabel : maxOutlierLabel);
- else
- {
- // First test value is enough to find the corresponding label in switch table to mark the case body
- // because all test values share the same case body and the same label, so the first test value will do.
- var testValExpr = cs.TestValues[0];
- var testValue = testValExpr is ConstantExpression constExpr ? ConvertValueObjectToLong(constExpr.Value) : 0L;
- var labelIndex = (int)(testValue - firstTestValue);
- il.DmarkLabel(switchTableLabels[labelIndex]);
- }
-
- if (!TryEmit(cs.Body, il, ref context, parent))
- return false;
- il.Demit(OpCodes.Br, endOfSwitchLabel);
- }
-
- labelPool.ReuseIfPossible(switchTableLabels);
- labelPool.MergeInto(ref _labelPool);
-
- if (defaultBody != null)
- {
- il.DmarkLabel(defaultBodyLabel);
- if (!TryEmit(defaultBody, il, ref context, parent))
- return false;
- }
-
- il.DmarkLabel(endOfSwitchLabel);
- return true;
- }
- }
-
- var switchValueIsNullable = switchValueType.IsNullable();
- Type switchNullableUnderlyingValueType = null;
- MethodInfo switchNullableHasValueMethod = null;
- FieldInfo switchNullableUnsafeValueField = null;
- if (switchValueIsNullable)
- {
- switchNullableUnderlyingValueType = Nullable.GetUnderlyingType(switchValueType);
- switchNullableHasValueMethod = switchValueType.GetNullableHasValueGetterMethod();
- switchNullableUnsafeValueField = switchValueType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod();
- }
-
- var checkType = switchNullableUnderlyingValueType ?? switchValueType;
- var equalityMethod = customEqualMethod != null
- ? customEqualMethod
- : !checkType.IsPrimitive && !checkType.IsEnum
- ? FindBinaryOperandMethod("op_Equality", switchValueType, switchValueType, switchValueType, typeof(bool))
- ?? _objectEqualsMethod
- : null;
-
- var isEqualityMethodForUnderlyingNullable = false;
- int param0ByRefIndex = -1, param1ByRefIndex = -1;
- if (equalityMethod != null)
- {
- switchValueParent |= ParentFlags.Call;
- var paramInfos = equalityMethod.GetParameters();
- Debug.Assert(paramInfos.Length == 2);
- var paramType = paramInfos[0].ParameterType;
- isEqualityMethodForUnderlyingNullable = paramType == switchNullableUnderlyingValueType;
- if (paramType.IsByRef)
- param0ByRefIndex = 0;
- if (paramInfos[1].ParameterType.IsByRef)
- param1ByRefIndex = 1;
- }
-
- // Emit the switch value once and store it in the local variable for comparison in cases below
- if (!TryEmit(switchValueExpr, il, ref context, switchValueParent, param0ByRefIndex))
- return false;
-
- if (caseCount == 0) // see #440
- {
- il.Demit(OpCodes.Pop); // remove the switch value result
- return defaultBody == null || TryEmit(defaultBody, il, ref context, parent);
- }
-
- var switchValueVar = EmitStoreLocalVariable(il, switchValueType);
-
- var switchEndLabel = il.DefineLabel();
-
-#if TESTING || DEBUG
- // Check the heap/pool part of the labels in debug mode
- SmallList, ProvidedArrayPool>> caseLabels = default;
-#else
- SmallList, ProvidedArrayPool>> caseLabels = default;
-#endif
- caseLabels.Pool.Init(_labelPool, 8);
-
- for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex)
- {
- var cs = cases[caseIndex];
- var caseBodyLabel = il.DefineLabel();
- caseLabels.Add(caseBodyLabel);
-
- foreach (var caseTestValue in cs.TestValues)
- {
- if (!switchValueIsNullable)
- {
- EmitLoadLocalVariable(il, switchValueVar);
- if (!TryEmit(caseTestValue, il, ref context, switchValueParent, param1ByRefIndex))
- return false;
- if (equalityMethod == null)
- {
- il.Demit(OpCodes.Beq, caseBodyLabel);
- continue;
- }
-
- if (!EmitMethodCall(il, equalityMethod))
- return false;
- il.Demit(OpCodes.Brtrue, caseBodyLabel);
- continue;
- }
-
- if (equalityMethod != null & !isEqualityMethodForUnderlyingNullable)
- {
- EmitLoadLocalVariable(il, switchValueVar);
- if (!TryEmit(caseTestValue, il, ref context, switchValueParent, param1ByRefIndex) ||
- !EmitMethodCall(il, equalityMethod))
- return false;
- il.Demit(OpCodes.Brtrue, caseBodyLabel);
- continue;
- }
-
- if (equalityMethod == null)
- {
- // short-circuit the comparison with the null, if the switch value has value == false the let's do a Brfalse
- if (caseTestValue is ConstantExpression r && r.Value == null)
- {
- EmitLoadLocalVariableAddress(il, switchValueVar);
- EmitMethodCall(il, switchNullableHasValueMethod);
- il.Demit(OpCodes.Brfalse, caseBodyLabel);
- continue;
- }
- }
-
- // Compare the switch value with the case value via Ceq or comparison method and then compare the HasValue of both
- EmitLoadLocalVariableAddress(il, switchValueVar);
- il.Demit(OpCodes.Ldfld, switchNullableUnsafeValueField);
- if (!TryEmit(caseTestValue, il, ref context, switchValueParent, param1ByRefIndex))
- return false;
- var caseValueVar = EmitStoreAndLoadLocalVariableAddress(il, switchValueType);
- il.Demit(OpCodes.Ldfld, switchNullableUnsafeValueField);
- if (equalityMethod == null)
- il.Demit(OpCodes.Ceq);
- else if (!EmitMethodCall(il, equalityMethod))
- return false;
-
- EmitLoadLocalVariableAddress(il, switchValueVar);
- EmitMethodCall(il, switchNullableHasValueMethod);
- EmitLoadLocalVariableAddress(il, caseValueVar);
- EmitMethodCall(il, switchNullableHasValueMethod);
- il.Demit(OpCodes.Ceq);
-
- il.Demit(OpCodes.And); // both the Nullable values and HashValue results need to be true
- il.Demit(OpCodes.Brtrue, caseBodyLabel);
- }
- }
-
- if (defaultBody != null && !TryEmit(defaultBody, il, ref context, parent))
- return false;
- il.Demit(OpCodes.Br, switchEndLabel);
-
- for (var caseIndex = 0; caseIndex < caseCount; ++caseIndex)
- {
- il.DmarkLabel(caseLabels[caseIndex]);
- var cs = cases[caseIndex];
- if (!TryEmit(cs.Body, il, ref context, parent))
- return false;
-
- il.Demit(OpCodes.Br, switchEndLabel);
- }
-
- caseLabels.FreePooled();
- caseLabels.Pool.MergeInto(ref _labelPool);
-
- il.DmarkLabel(switchEndLabel);
- return true;
- }
-
- // todo: @perf cache found method, because for some cases there many methods to search from, e.g. 157 methods in BigInteger
- private static MethodInfo FindBinaryOperandMethod(
- string methodName, Type sourceType, Type leftOpType, Type rightOpType, Type resultType)
- {
- var methods = sourceType.GetMethods();
- for (var i = 0; i < methods.Length; i++)
- {
- var m = methods[i];
- if (m.IsSpecialName && m.IsStatic && m.Name == methodName && m.ReturnType == resultType)
- {
- var ps = m.GetParameters();
- if (ps.Length == 2 && ps[0].ParameterType == leftOpType && ps[1].ParameterType == rightOpType)
- return m;
- }
- }
- return null;
- }
-
- private static bool TryEmitComparison(
- Expression left, Expression right, Type exprType, ExpressionType nodeType,
- ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var leftType = left.Type;
- var leftIsNullable = leftType.IsNullable();
- var rightType = right.Type;
-
- // If one operand is `null` then the equality comparison can be simplified to `ldnull, ceq`
- var rightIsNull = IsNullContainingExpression(right);
- var comparingToRightNull = rightIsNull & rightType.IsClass;
-
- // Coalesce the right object type to the more specific left type
- if (comparingToRightNull & rightType == typeof(object))
- rightType = leftType;
-
- var leftIsNull = IsNullContainingExpression(left);
- var comparingToLeftNull = leftIsNull & leftType.IsClass;
- if (!comparingToRightNull && comparingToLeftNull & leftType == typeof(object))
- leftType = rightType;
-
- var operandParent = parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess;
-
- // short-circuit the comparison with null on the right
- var isEqualityOp = nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual;
- if (isEqualityOp)
- {
- if (leftIsNullable & rightIsNull)
- {
- if (!TryEmit(left, il, ref context, operandParent))
- return false;
-
- // See #341 `Nullable_decimal_parameter_with_decimal_constant_comparison_cases`
- if (!context.LastEmitIsAddress && !(left is ParameterExpression p && p.IsByRef)) // Nullable type does not track IsByRef for some reason, so we check the param explicitly, see #461 `Case_equal_nullable_and_object_null`
- EmitStoreAndLoadLocalVariableAddress(il, leftType);
-
- EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod());
- if (nodeType == ExpressionType.Equal)
- EmitEqualToZeroOrNull(il);
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- if (leftIsNull && rightType.IsNullable())
- {
- if (!TryEmit(right, il, ref context, operandParent))
- return false;
-
- if (!context.LastEmitIsAddress && !(right is ParameterExpression p && p.IsByRef))
- EmitStoreAndLoadLocalVariableAddress(il, rightType);
-
- EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod());
- if (nodeType == ExpressionType.Equal)
- EmitEqualToZeroOrNull(il);
- return il.EmitPopIfIgnoreResult(parent);
- }
- }
-
- var lVarIndex = -1;
- var rightIsComplexExpression = false;
- // just load the `null` later when done with the right operand, without need for go to nested TryEmit call
- // and store, load the left result for the complex expressions, see `IsComplexExpression` and #422
- if (!leftIsNull)
- {
- if (!TryEmit(left, il, ref context, operandParent))
- return false;
-
- // save the left result to restore it later after the complex expression, see #422
- if (rightIsComplexExpression = right.IsComplexExpression())
- lVarIndex = EmitStoreLocalVariable(il, leftType);
- else if (leftIsNullable)
- {
- lVarIndex = EmitStoreAndLoadLocalVariableAddress(il, leftType);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- leftType = Nullable.GetUnderlyingType(leftType);
- }
- }
-
- if (rightIsNull)
- il.Demit(OpCodes.Ldnull);
- else if (!TryEmit(right, il, ref context, operandParent))
- return false;
-
- if (comparingToLeftNull | comparingToRightNull ||
- (leftType != rightType && leftType.IsClass && rightType.IsClass &&
- (leftType == typeof(object) | rightType == typeof(object))))
- {
- // If the operation is not Equal or NotEqual then comparison with null is not possible
- if (!isEqualityOp)
- return false;
-
- if (leftIsNull)
- il.Demit(OpCodes.Ldnull);
- else if (rightIsComplexExpression)
- EmitLoadLocalVariable(il, lVarIndex); // the order of comparison does not matter, because equality ops are commutative
-
- il.Demit(OpCodes.Ceq);
- if (nodeType == ExpressionType.NotEqual)
- EmitEqualToZeroOrNull(il);
-
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- var rVarIndex = -1;
- if (rightIsComplexExpression)
- {
- rVarIndex = EmitStoreLocalVariable(il, rightType);
- if (!leftIsNullable)
- EmitLoadLocalVariable(il, lVarIndex);
- else
- {
- EmitLoadLocalVariableAddress(il, lVarIndex);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- leftType = Nullable.GetUnderlyingType(leftType);
- }
-
- if (!rightType.IsNullable())
- EmitLoadLocalVariable(il, rVarIndex);
- else
- {
- EmitLoadLocalVariableAddress(il, rVarIndex);
- il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- rightType = Nullable.GetUnderlyingType(rightType);
- }
- }
- else if (leftIsNull)
- {
- // here we're handling only non-nullable right, the nullable right with null left is handled above
- rVarIndex = EmitStoreLocalVariable(il, rightType);
- il.Demit(OpCodes.Ldnull);
- EmitLoadLocalVariable(il, rVarIndex);
- }
- else if (rightType.IsNullable())
- {
- rVarIndex = EmitStoreAndLoadLocalVariableAddress(il, rightType);
- il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- rightType = Nullable.GetUnderlyingType(rightType);
- }
-
- if (!leftType.IsPrimitive && !leftType.IsEnum)
- {
- var methodName
- = nodeType == ExpressionType.Equal ? "op_Equality"
- : nodeType == ExpressionType.NotEqual ? "op_Inequality"
- : nodeType == ExpressionType.GreaterThan ? "op_GreaterThan"
- : nodeType == ExpressionType.GreaterThanOrEqual ? "op_GreaterThanOrEqual"
- : nodeType == ExpressionType.LessThan ? "op_LessThan"
- : nodeType == ExpressionType.LessThanOrEqual ? "op_LessThanOrEqual"
- : null;
- if (methodName == null)
- return false;
-
- var method = FindBinaryOperandMethod(methodName, leftType, leftType, rightType, typeof(bool));
- if (method == null & leftType != rightType)
- method = FindBinaryOperandMethod(methodName, rightType, leftType, rightType, typeof(bool));
- if (method != null)
- {
- var ok = EmitMethodCall(il, method);
- if (leftIsNullable)
- goto nullableCheck;
- return ok;
- }
-
- if (!isEqualityOp)
- return false; // todo: @unclear what is the alternative?
-
- EmitMethodCall(il, _objectEqualsMethod);
- if (nodeType == ExpressionType.NotEqual) // invert result for not equal
- EmitEqualToZeroOrNull(il);
-
- if (leftIsNullable)
- goto nullableCheck;
-
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- // handle primitives comparison
- switch (nodeType)
- {
- case ExpressionType.Equal:
- il.Demit(OpCodes.Ceq);
- break;
- case ExpressionType.NotEqual:
- il.Demit(OpCodes.Ceq);
- EmitEqualToZeroOrNull(il);
- break;
- case ExpressionType.LessThan:
- il.Demit(OpCodes.Clt);
- break;
- case ExpressionType.GreaterThan:
- il.Demit(OpCodes.Cgt);
- break;
- case ExpressionType.GreaterThanOrEqual:
- // simplifying by using the LessThen (Clt) and comparing with negative outcome (Ceq 0)
- if (leftType.IsUnsigned() && rightType.IsUnsigned() ||
- (leftType.IsFloatingPoint() || rightType.IsFloatingPoint()))
- il.Demit(OpCodes.Clt_Un);
- else
- il.Demit(OpCodes.Clt);
- EmitEqualToZeroOrNull(il);
- break;
- case ExpressionType.LessThanOrEqual:
- // simplifying by using the GreaterThen (Cgt) and comparing with negative outcome (Ceq 0)
- if (leftType.IsUnsigned() && rightType.IsUnsigned() ||
- (leftType.IsFloatingPoint() || rightType.IsFloatingPoint()))
- il.Demit(OpCodes.Cgt_Un);
- else
- il.Demit(OpCodes.Cgt);
- EmitEqualToZeroOrNull(il);
- break;
-
- default:
- return false;
- }
-
- nullableCheck:
- if (leftIsNullable)
- {
- var leftNullableHasValueGetterMethod = left.Type.GetNullableHasValueGetterMethod(); // asking from the left.Type because leftType now is set to the underlying type
-
- EmitLoadLocalVariableAddress(il, lVarIndex);
- EmitMethodCall(il, leftNullableHasValueGetterMethod);
-
- var isLiftedToNull = exprType == typeof(bool?);
- var leftHasValueVar = -1;
- if (isLiftedToNull)
- EmitStoreAndLoadLocalVariable(il, leftHasValueVar = il.GetNextLocalVarIndex(typeof(bool)));
-
- // ReSharper disable once AssignNullToNotNullAttribute
- EmitLoadLocalVariableAddress(il, rVarIndex);
- EmitMethodCall(il, leftNullableHasValueGetterMethod);
-
- var rightHasValueVar = -1;
- if (isLiftedToNull)
- EmitStoreAndLoadLocalVariable(il, rightHasValueVar = il.GetNextLocalVarIndex(typeof(bool)));
-
- switch (nodeType)
- {
- case ExpressionType.Equal:
- il.Demit(OpCodes.Ceq); // compare both HasValue calls
- il.Demit(OpCodes.And); // both results need to be true
- break;
-
- case ExpressionType.NotEqual:
- il.Demit(OpCodes.Ceq);
- EmitEqualToZeroOrNull(il);
- il.Demit(OpCodes.Or);
- break;
-
- case ExpressionType.LessThan:
- case ExpressionType.GreaterThan:
- case ExpressionType.LessThanOrEqual:
- case ExpressionType.GreaterThanOrEqual:
- // left.HasValue `and` right.HasValue
- il.Demit(OpCodes.And);
- // `and` the prev result of comparison operation
- il.Demit(OpCodes.And);
- break;
-
- default:
- return false;
- }
-
- if (isLiftedToNull)
- {
- var resultLabel = il.DefineLabel();
- var isNullLabel = il.DefineLabel();
- EmitLoadLocalVariable(il, leftHasValueVar);
- il.Demit(OpCodes.Brfalse_S, isNullLabel); // safe: jumps over EmitLoadLocalVariable (1-4 bytes) + Brtrue_S (2 bytes) = 3-6 bytes
- EmitLoadLocalVariable(il, rightHasValueVar);
- il.Demit(OpCodes.Brtrue_S, resultLabel); // safe: jumps over Pop (1 byte) + Ldnull (1 byte) = 2 bytes
- il.DmarkLabel(isNullLabel);
- il.Demit(OpCodes.Pop);
- il.Demit(OpCodes.Ldnull);
- il.DmarkLabel(resultLabel);
- }
- }
-
- return il.EmitPopIfIgnoreResult(parent);
- }
-
- private static bool TryEmitArithmetic(Expression left, Expression right, ExpressionType nodeType, Type exprType,
- ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- var flags = (parent
- & ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall |
- ParentFlags.LambdaCall | ParentFlags.ReturnByRef))
- | ParentFlags.Arithmetic;
-
- var noNullableValueLabel = default(Label);
- var leftType = left.Type;
- var leftIsNullable = leftType.IsNullable();
- var leftVar = -1;
- var leftValueVar = -1;
- if (leftIsNullable)
- {
- noNullableValueLabel = il.DefineLabel();
- if (!TryEmit(left, il, ref context, flags | ParentFlags.InstanceCall))
- return false;
-
- leftVar = EmitStoreAndLoadLocalVariableAddress(il, leftType);
- EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod());
- il.Demit(OpCodes.Brfalse, noNullableValueLabel);
-
- EmitLoadLocalVariableAddress(il, leftVar);
- il.Demit(OpCodes.Ldfld, leftType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- leftValueVar = EmitStoreLocalVariable(il, Nullable.GetUnderlyingType(leftType));
- }
- else if (!TryEmit(left, il, ref context, flags))
- return false;
-
- var rightIsNullable = false;
- if (right == null) // indicates the increment/decrement operation
- {
- EmitIncOrDec(il, nodeType == ExpressionType.Add);
- }
- else
- {
- // Stores the left value for later to restore it after the complex right emit,
- // it prevents the problems in cases of right being a block, try-catch, etc.
- // see `Using_try_finally_as_arithmetic_operand_use_void_block_in_finally`
- var rightType = right.Type;
- if (leftValueVar == -1 && right.IsComplexExpression())
- leftValueVar = EmitStoreLocalVariable(il, leftType);
-
- var rightVar = -1;
- var rightValueVar = -1;
- rightIsNullable = rightType.IsNullable();
- if (rightIsNullable)
- {
- if (!TryEmit(right, il, ref context, flags | ParentFlags.InstanceCall))
- return false;
-
- rightVar = EmitStoreAndLoadLocalVariableAddress(il, rightType);
-
- EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod());
- il.Demit(OpCodes.Brfalse, noNullableValueLabel);
-
- EmitLoadLocalVariableAddress(il, rightVar);
- il.Demit(OpCodes.Ldfld, rightType.GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
- rightValueVar = EmitStoreLocalVariable(il, Nullable.GetUnderlyingType(rightType));
- }
- else if (!TryEmit(right, il, ref context, flags))
- return false;
-
- // Means that it was complex right and the result of the left operation was stored
- // and should be restored now, so the left and right go in order before the arithmetic operation
- if (leftValueVar != -1)
- {
- if (rightValueVar == -1)
- rightValueVar = EmitStoreLocalVariable(il, rightType);
- EmitLoadLocalVariable(il, leftValueVar);
- EmitLoadLocalVariable(il, rightValueVar);
- }
-
- if (!TryEmitArithmeticOperation(leftType, rightType, nodeType, exprType, il))
- return false;
- }
-
- if (leftIsNullable | rightIsNullable)
- {
- var valueLabel = il.DefineLabel();
- il.Demit(OpCodes.Br, valueLabel);
-
- il.DmarkLabel(noNullableValueLabel);
-
- if (exprType.IsNullable())
- {
- EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType));
- var endLabel = il.DefineLabel();
- il.Demit(OpCodes.Br_S, endLabel);
- il.DmarkLabel(valueLabel);
- il.Demit(OpCodes.Newobj, exprType.GetNullableConstructor());
- il.DmarkLabel(endLabel);
- }
- else
- {
- il.Demit(OpCodes.Ldc_I4_0);
- il.DmarkLabel(valueLabel);
- }
- }
-
- il.EmitPopIfIgnoreResult(parent);
- return true;
- }
-
- private static MethodInfo _stringStringConcatMethod, _stringObjectConcatMethod;
- private static MethodInfo GetStringConcatMethod(Type paraType)
- {
- var methods = typeof(string).GetMethods();
- for (var i = 0; i < methods.Length; i++)
- {
- var m = methods[i];
- if (m.IsStatic && m.Name == "Concat" &&
- m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == paraType)
- return m;
- }
- return null;
- }
-
- private static bool TryEmitArithmeticOperation(Type leftType, Type rightType, ExpressionType arithmeticNodeType, Type exprType, ILGenerator il)
- {
- if (!exprType.IsPrimitive)
- {
- if (exprType.IsNullable())
- exprType = Nullable.GetUnderlyingType(exprType);
-
- if (!exprType.IsPrimitive)
- {
- var opMethodName = arithmeticNodeType.GetArithmeticBinaryOperatorMethodName();
- if (opMethodName == null)
- return false; // todo: @feature should return specific error
-
- MethodInfo method = null;
- if (exprType != typeof(string))
- {
- // Note, that the result operation Type may be different from the operand Type,
- // e.g. `TimeSpan op_Subtraction(DateTime, DateTime)`, that mean we should look
- // for the specific method in the operand types, then in the result (expr) type.
- method = FindBinaryOperandMethod(opMethodName, leftType, leftType, rightType, exprType);
- if (method == null & leftType != rightType)
- method = FindBinaryOperandMethod(opMethodName, rightType, leftType, rightType, exprType);
- if (method == null & leftType != exprType & rightType != exprType)
- method = FindBinaryOperandMethod(opMethodName, exprType, leftType, rightType, exprType);
- // todo: @feature should return specific error
- return method != null && EmitMethodCall(il, method);
- }
-
- method = leftType != rightType | leftType != typeof(string)
- ? _stringObjectConcatMethod ?? (_stringObjectConcatMethod = GetStringConcatMethod(typeof(object)))
- : _stringStringConcatMethod ?? (_stringStringConcatMethod = GetStringConcatMethod(typeof(string)));
-
- return method != null && EmitMethodCallOrVirtualCall(il, method);
- }
- }
-
- var opCode = arithmeticNodeType switch
- {
- ExpressionType.Add => OpCodes.Add,
- ExpressionType.AddChecked => exprType.IsUnsigned() ? OpCodes.Add_Ovf_Un : OpCodes.Add_Ovf,
- ExpressionType.Subtract => OpCodes.Sub,
- ExpressionType.SubtractChecked => exprType.IsUnsigned() ? OpCodes.Sub_Ovf_Un : OpCodes.Sub_Ovf,
- ExpressionType.Multiply => OpCodes.Mul,
- ExpressionType.MultiplyChecked => exprType.IsUnsigned() ? OpCodes.Mul_Ovf_Un : OpCodes.Mul_Ovf,
- ExpressionType.Divide => OpCodes.Div,
- ExpressionType.Modulo => exprType.IsUnsigned() ? OpCodes.Rem_Un : OpCodes.Rem,
- ExpressionType.And => OpCodes.And,
- ExpressionType.Or => OpCodes.Or,
- ExpressionType.ExclusiveOr => OpCodes.Xor,
- ExpressionType.LeftShift => OpCodes.Shl,
- ExpressionType.RightShift => exprType.IsUnsigned() ? OpCodes.Shr_Un : OpCodes.Shr,
- ExpressionType.Power => OpCodes.Call,
- _ => throw new NotSupportedException("Unsupported arithmetic operation: " + arithmeticNodeType)
- };
-
- if (opCode.Equals(OpCodes.Call))
- il.Demit(OpCodes.Call, typeof(Math).FindMethod("Pow"));
- else
- il.Demit(opCode);
- return true;
- }
-
- private static bool TryEmitLogicalOperator(BinaryExpression expr, ExpressionType nodeType,
- ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- if (!TryEmit(expr.Left, il, ref context, parent))
- return false;
-
- var labelSkipRight = il.DefineLabel();
- il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Brfalse : OpCodes.Brtrue, labelSkipRight);
-
- if (!TryEmit(expr.Right, il, ref context, parent))
- return false;
-
- var labelDone = il.DefineLabel();
- il.Demit(OpCodes.Br_S, labelDone); // safe: jumps over Ldc_I4_0 or Ldc_I4_1 (1 byte) = 1 byte
-
- il.DmarkLabel(labelSkipRight); // label the second branch
- il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1);
- il.DmarkLabel(labelDone);
-
- return true;
- }
-
- private static bool IsNullContainingExpression(Expression expr) =>
- expr is DefaultExpression ld && (ld.Type.IsClass || ld.Type.IsNullable()) ||
- expr is ConstantExpression lc && lc.Value == null;
-
- private static bool TryEmitConditional(Expression testExpr, Expression ifTrueExpr, Expression ifFalseExpr,
- ILGenerator il, ref CompilerContext context, ParentFlags parent)
- {
- testExpr = Tools.TryReduceConditionalTest(testExpr);
- var testNodeType = testExpr.NodeType;
-
- // Detect a simplistic case when we can use `Brtrue` or `Brfalse`.
- // We are checking the negative result to go into the `IfFalse` branch,
- // because for `IfTrue` we don't need to jump and just need to proceed emitting the `IfTrue` expression
- //
- // The cases:
- // `x == true` => `Brfalse`
- // `x != true` => `Brtrue`
- // `x == false` => `Brtrue`
- // `x != false` => `Brfalse`
- // `x == null` => `Brtrue`
- // `x != null` => `Brfalse`
- // `x == 0` => `Brtrue`
- // `x != 0` => `Brfalse`
-
- var useBrFalseOrTrue = -1; // 0 - is comparison with Zero (0, null, false), 1 - is comparison with (true)
- Type nullOfValueType = null;
- if (testExpr is BinaryExpression tb &&
- (testNodeType == ExpressionType.Equal | testNodeType == ExpressionType.NotEqual))
- {
- var testLeftExpr = tb.Left;
- var testRightExpr = tb.Right;
-
- Expression oppositeTestExpr = null;
- var sideConstExpr = testRightExpr as ConstantExpression ?? testLeftExpr as ConstantExpression;
- if (sideConstExpr != null)
- {
- oppositeTestExpr = sideConstExpr == testLeftExpr ? testRightExpr : testLeftExpr;
- var sideConstVal = sideConstExpr.Value;
- if (sideConstVal == null) // todo: @perf we need to optimize for the Default as well
- {
- useBrFalseOrTrue = 0;
- if (oppositeTestExpr.Type.IsNullable())
- nullOfValueType = oppositeTestExpr.Type;
- }
- else if (sideConstVal is bool boolConst)
- useBrFalseOrTrue = boolConst ? 1 : 0;
- else if (sideConstVal is int intConst && intConst == 0)
- useBrFalseOrTrue = 0; // Brtrue does not work for `1`, you need to use Beq, or similar
- else if (sideConstVal is byte bytConst && bytConst == 0)
- useBrFalseOrTrue = 0;
- }
- else
- {
- var sideDefaultExpr = testRightExpr as DefaultExpression ?? testLeftExpr as DefaultExpression;
- if (sideDefaultExpr != null)
- {
- oppositeTestExpr = sideDefaultExpr == testLeftExpr ? testRightExpr : testLeftExpr;
- var testSideType = sideDefaultExpr.Type;
- // except decimal, because its 0 is Decimal.Zero a struct and is not working with Brtrue/Brfalse
- if (testSideType.IsPrimitiveWithZeroDefaultExceptDecimal())
- useBrFalseOrTrue = 0;
- else if (testSideType.IsClass || testSideType.IsNullable())
- {
- useBrFalseOrTrue = 0;
- if (oppositeTestExpr.Type.IsNullable())
- nullOfValueType = oppositeTestExpr.Type;
- }
- }
- }
-
- if (useBrFalseOrTrue != -1 &&
- !TryEmit(oppositeTestExpr, il, ref context, parent & ~ParentFlags.IgnoreResult))
- return false;
- }
-
- if (useBrFalseOrTrue == -1 &&
- !TryEmit(testExpr, il, ref context, parent & ~ParentFlags.IgnoreResult))
- return false;
-
- if (nullOfValueType != null)
- {
- if (!context.LastEmitIsAddress)
- EmitStoreAndLoadLocalVariableAddress(il, nullOfValueType);
- EmitMethodCall(il, nullOfValueType.GetNullableHasValueGetterMethod());
- }
-
- var labelIfFalse = il.DefineLabel();
-
- // todo: @perf try to recognize the patterns like `b == 1` and replace Ceq, Brtrue with Beq, and respectively Ceq, Brfalse with Bne_Un
- // for this we need to inline here the logic from the TryEmitComparison.
- if ((testNodeType == ExpressionType.Equal & useBrFalseOrTrue == 0) ||
- (testNodeType == ExpressionType.NotEqual & useBrFalseOrTrue == 1))
- il.Demit(OpCodes.Brtrue, labelIfFalse);
- else
- il.Demit(OpCodes.Brfalse, labelIfFalse);
-
- if (!TryEmit(ifTrueExpr, il, ref context, parent))
- return false;
-
- if (ifFalseExpr.NodeType == ExpressionType.Default && ifFalseExpr.Type == typeof(void))
- il.DmarkLabel(labelIfFalse);
- else
- {
- var labelDone = il.DefineLabel();
- il.Demit(OpCodes.Br, labelDone);
- il.DmarkLabel(labelIfFalse);
- if (!TryEmit(ifFalseExpr, il, ref context, parent))
- return false;
- il.DmarkLabel(labelDone);
- }
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public static void EmitEqualToZeroOrNull(ILGenerator il)
- {
- il.Demit(OpCodes.Ldc_I4_0); // OpCodes.Not does not work here because it is a bitwise operation
- il.Demit(OpCodes.Ceq);
- }
-
- /// Get the advantage of the optimized specialized EmitCall method
- [MethodImpl((MethodImplOptions)256)]
- public static bool EmitMethodCallOrVirtualCall(ILGenerator il, MethodInfo method)
- {
- il.Demit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);
- // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods,
- // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo).
- // So for now the varargs methods are not supported yet.
- return (method.CallingConvention & CallingConventions.VarArgs) == 0;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public static bool EmitVirtualMethodCall(ILGenerator il, MethodInfo method)
- {
- il.Demit(OpCodes.Callvirt, method);
- // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods,
- // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo).
- // So for now the varargs methods are not supported yet.
- return (method.CallingConvention & CallingConventions.VarArgs) == 0;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- public static bool EmitMethodCall(ILGenerator il, MethodInfo method)
- {
- il.Demit(OpCodes.Call, method);
- // todo: @feature EmitCall is specifically for the varags method and not for normal C# conventions methods,
- // for those you need to call Emit(OpCodes.Call|Callvirt, methodInfo).
- // So for now the varargs methods are not supported yet.
- return (method.CallingConvention & CallingConventions.VarArgs) == 0;
- }
-
- /// Same as EmitMethodCall which checks the method for null first, and returns false if it is null.
- [MethodImpl((MethodImplOptions)256)]
- public static bool EmitMethodCallCheckForNull(ILGenerator il, MethodInfo method) =>
- method != null && EmitMethodCall(il, method);
-
- /// Same as EmitMethodCallOrVirtualCall which checks the method for null first, and returns false if it is null.
- [MethodImpl((MethodImplOptions)256)]
- public static bool EmitMethodCallOrVirtualCallCheckForNull(ILGenerator il, MethodInfo method) =>
- method != null && EmitMethodCallOrVirtualCall(il, method);
-
- /// Efficiently emit the int constant
- [MethodImpl((MethodImplOptions)256)]
- public static void EmitLoadConstantInt(ILGenerator il, int i)
- {
- switch (i)
- {
- case -1: il.Demit(OpCodes.Ldc_I4_M1); break;
- case 0: il.Demit(OpCodes.Ldc_I4_0); break;
- case 1: il.Demit(OpCodes.Ldc_I4_1); break;
- case 2: il.Demit(OpCodes.Ldc_I4_2); break;
- case 3: il.Demit(OpCodes.Ldc_I4_3); break;
- case 4: il.Demit(OpCodes.Ldc_I4_4); break;
- case 5: il.Demit(OpCodes.Ldc_I4_5); break;
- case 6: il.Demit(OpCodes.Ldc_I4_6); break;
- case 7: il.Demit(OpCodes.Ldc_I4_7); break;
- case 8: il.Demit(OpCodes.Ldc_I4_8); break;
- default:
- if (i > -129 && i < 128)
- il.Demit(OpCodes.Ldc_I4_S, (sbyte)i);
- else
- il.Demit(OpCodes.Ldc_I4, i);
- break;
- }
- }
-
- /// Efficiently emit the long constant based on its value as int or long
- [MethodImpl((MethodImplOptions)256)]
- public static void EmitLoadConstantLong(ILGenerator il, long i)
- {
- if (i >= int.MinValue && i <= int.MaxValue)
- EmitLoadConstantInt(il, (int)i);
- else
- il.Demit(OpCodes.Ldc_I8, i);
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static void EmitLoadLocalVariableAddress(ILGenerator il, int location)
- {
- if ((uint)location <= byte.MaxValue)
- il.Demit(OpCodes.Ldloca_S, (byte)location);
- else
- il.Demit(OpCodes.Ldloca, (short)location);
- }
-
- /// Load local variable on stack
- [MethodImpl((MethodImplOptions)256)]
- public static bool EmitLoadLocalVariable(ILGenerator il, int location)
- {
- if (location == 0)
- il.Demit(OpCodes.Ldloc_0);
- else if (location == 1)
- il.Demit(OpCodes.Ldloc_1);
- else if (location == 2)
- il.Demit(OpCodes.Ldloc_2);
- else if (location == 3)
- il.Demit(OpCodes.Ldloc_3);
- else if ((uint)location <= byte.MaxValue)
- il.Demit(OpCodes.Ldloc_S, (byte)location);
- else
- il.Demit(OpCodes.Ldloc, (short)location);
- return true;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static bool EmitIncOrDec(ILGenerator il, bool isInc = false)
- {
- il.Demit(OpCodes.Ldc_I4_1);
- il.Demit(isInc ? OpCodes.Add : OpCodes.Sub);
- return true;
- }
-
- /// Store the variable location on stack
- [MethodImpl((MethodImplOptions)256)]
- public static void EmitStoreLocalVariable(ILGenerator il, int location)
- {
- if (location == 0)
- il.Demit(OpCodes.Stloc_0);
- else if (location == 1)
- il.Demit(OpCodes.Stloc_1);
- else if (location == 2)
- il.Demit(OpCodes.Stloc_2);
- else if (location == 3)
- il.Demit(OpCodes.Stloc_3);
- else if ((uint)location <= byte.MaxValue)
- il.Demit(OpCodes.Stloc_S, (byte)location);
- else
- il.Demit(OpCodes.Stloc, (short)location);
- }
-
- /// Get and store the variable of the type on stack
- [MethodImpl((MethodImplOptions)256)]
- public static int EmitStoreLocalVariable(ILGenerator il, Type type)
- {
- var location = il.GetNextLocalVarIndex(type);
- EmitStoreLocalVariable(il, location);
- return location;
- }
-
- /// Stores and loads the variable
- [MethodImpl((MethodImplOptions)256)]
- public static void EmitStoreAndLoadLocalVariable(ILGenerator il, int location)
- {
- if (location == 0)
- {
- il.Demit(OpCodes.Stloc_0);
- il.Demit(OpCodes.Ldloc_0);
- }
- else if (location == 1)
- {
- il.Demit(OpCodes.Stloc_1);
- il.Demit(OpCodes.Ldloc_1);
- }
- else if (location == 2)
- {
- il.Demit(OpCodes.Stloc_2);
- il.Demit(OpCodes.Ldloc_2);
- }
- else if (location == 3)
- {
- il.Demit(OpCodes.Stloc_3);
- il.Demit(OpCodes.Ldloc_3);
- }
- else if ((uint)location <= byte.MaxValue)
- {
- il.Demit(OpCodes.Stloc_S, (byte)location);
- il.Demit(OpCodes.Ldloc_S, (byte)location);
- }
- else
- {
- il.Demit(OpCodes.Stloc, (short)location);
- il.Demit(OpCodes.Ldloc, (short)location);
- }
- }
-
- /// Stores and loads the variable, and returns it
- public static int EmitStoreAndLoadLocalVariable(ILGenerator il, Type t)
- {
- var location = il.GetNextLocalVarIndex(t);
- EmitStoreAndLoadLocalVariable(il, location);
- return location;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static void EmitStoreAndLoadLocalVariableAddress(ILGenerator il, int location)
- {
- // #if DEBUG
- // var ilLengthField = typeof(ILGenerator).GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic);
- // var ilStreamField = typeof(ILGenerator).GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic);
- // var ilLength = (int)ilLengthField.GetValue(il);
- // var ilStream = (byte[])ilStreamField.GetValue(il);
-
- // var ilMaxMidStackField = typeof(ILGenerator).GetField("m_maxMidStack", BindingFlags.Instance | BindingFlags.NonPublic);
- // var ilMaxMidStackCurField = typeof(ILGenerator).GetField("m_maxMidStackCur", BindingFlags.Instance | BindingFlags.NonPublic);
- // var ilMaxMidStack = (int)ilMaxMidStackField.GetValue(il);
- // var ilMaxMidStackCur = (int)ilMaxMidStackCurField.GetValue(il);
- // #endif
- if (location == 0)
- {
- // todo: @perf
- // the internal code for this is
- //
- // EnsureCapacity(3);
- // InternalEmit(opcode);
- // EnsureCapacity(4);
- // InternalEmit(opcode);
- // m_ILStream[m_length++] = (byte)arg;
- //
- // which translates to ->
- //
- // if (m_length + 7 >= m_ILStream.Length)
- // IncreaseCapacity(7);
- // // No stack change here cause 1st op decrease stack by 1 and second increase by 1
- // m_ILStream[m_length++] = (byte)OpCodes.Stloc_0.Value;
- // m_ILStream[m_length++] = (byte)OpCodes.Ldloca_S.Value;
- // m_ILStream[m_length++] = (byte)0; // we may no need it
- //
- il.Demit(OpCodes.Stloc_0);
- il.Demit(OpCodes.Ldloca_S, (byte)0);
- }
- else if (location == 1)
- {
- // todo: @perf we may introduce the EmitOne, EmitBatchNonStackModified(OpCode store, OpCode load, byte value), etc. method overloads
- //
- // if (ilLength + 7 < ilStream.Length)
- // {
- // ilStream[ilLength++] = (byte)OpCodes.Stloc_1.Value;
- // if (ilMaxMidStackCur + 1 > ilMaxMidStack)
- // ilMaxMidStackField.SetValue(il, ilMaxMidStackCur + 1);
- // ilStream[ilLength++] = (byte)OpCodes.Ldloca_S.Value;
- // ilStream[ilLength++] = (byte)1;
- // ilLengthField.SetValue(il, ilLength);
- // }
- // else
- // {
- il.Demit(OpCodes.Stloc_1);
- il.Demit(OpCodes.Ldloca_S, (byte)1);
- // }
- }
- else if (location == 2)
- {
- il.Demit(OpCodes.Stloc_2);
- il.Demit(OpCodes.Ldloca_S, (byte)2);
- }
- else if (location == 3)
- {
- il.Demit(OpCodes.Stloc_3);
- il.Demit(OpCodes.Ldloca_S, (byte)3);
- }
- else if ((uint)location <= byte.MaxValue)
- {
- il.Demit(OpCodes.Stloc_S, (byte)location);
- il.Demit(OpCodes.Ldloca_S, (byte)location);
- }
- else
- {
- il.Demit(OpCodes.Stloc, (short)location);
- il.Demit(OpCodes.Ldloca, (short)location);
- }
- }
-
- private static int EmitStoreAndLoadLocalVariableAddress(ILGenerator il, Type type)
- {
- var location = il.GetNextLocalVarIndex(type);
- EmitStoreAndLoadLocalVariableAddress(il, location);
- return location;
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static void EmitLoadArg(ILGenerator il, int paramIndex)
- {
- if (paramIndex == 0)
- il.Demit(OpCodes.Ldarg_0);
- else if (paramIndex == 1)
- il.Demit(OpCodes.Ldarg_1);
- else if (paramIndex == 2)
- il.Demit(OpCodes.Ldarg_2);
- else if (paramIndex == 3)
- il.Demit(OpCodes.Ldarg_3);
- else if ((uint)paramIndex <= byte.MaxValue)
- il.Demit(OpCodes.Ldarg_S, (byte)paramIndex);
- else
- il.Demit(OpCodes.Ldarg, (short)paramIndex);
- }
-
- [MethodImpl((MethodImplOptions)256)]
- private static void EmitLoadArgAddress(ILGenerator il, int paramIndex)
- {
- if ((uint)paramIndex <= byte.MaxValue)
- il.Demit(OpCodes.Ldarga_S, (byte)paramIndex);
- else
- il.Demit(OpCodes.Ldarga, (short)paramIndex);
- }
-
- /// Tries to interpret and emit the result IL
- /// In case of exception return false, to allow FEC emit normally and throw in the invocation phase
- public static bool TryInterpretAndEmitResult(Expression expr, ILGenerator il, ParentFlags parent, CompilerFlags flags)
- {
- var type = expr.Type;
- Debug.Assert(type.IsPrimitive);
- if ((flags & CompilerFlags.DisableInterpreter) != 0)
- return false;
-
- var typeCode = Type.GetTypeCode(type);
- try
- {
- switch (typeCode)
- {
- case TypeCode.Boolean:
- var resultBool = false;
- if (!Interpreter.TryInterpretBool(ref resultBool, expr, expr.NodeType))
- return false;
- il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- break;
-
- case TypeCode.Int32:
- int resultInt = 0;
- if (!Interpreter.TryInterpretInt(ref resultInt, expr, expr.NodeType))
- return false;
- if ((parent & ParentFlags.IgnoreResult) == 0)
- EmitLoadConstantInt(il, resultInt);
- break;
- case TypeCode.Decimal:
- decimal resultDec = default;
- if (!Interpreter.TryInterpretDecimal(ref resultDec, expr, expr.NodeType))
- return false;
- if ((parent & ParentFlags.IgnoreResult) == 0)
- EmitDecimalConstant(resultDec, il);
- break;
- default:
- Interpreter.PValue resultVal = default;
- if (!Interpreter.TryInterpretPrimitiveValue(ref resultVal, expr, typeCode, expr.NodeType))
- return false;
- if ((parent & ParentFlags.IgnoreResult) == 0)
- switch (typeCode)
- {
- case TypeCode.Char:
- EmitLoadConstantInt(il, resultVal.CharValue);
- break;
- case TypeCode.SByte:
- EmitLoadConstantInt(il, resultVal.SByteValue);
- break;
- case TypeCode.Byte:
- EmitLoadConstantInt(il, resultVal.ByteValue);
- break;
- case TypeCode.Int16:
- EmitLoadConstantInt(il, resultVal.Int16Value);
- break;
- case TypeCode.UInt16:
- EmitLoadConstantInt(il, resultVal.UInt16Value);
- break;
- case TypeCode.Int32:
- EmitLoadConstantInt(il, resultVal.Int32Value);
- break;
- case TypeCode.UInt32:
- unchecked
- {
- EmitLoadConstantInt(il, (int)resultVal.UInt32Value);
- }
- break;
- case TypeCode.Int64:
- il.Demit(OpCodes.Ldc_I8, resultVal.Int64Value);
- break;
- case TypeCode.UInt64:
- unchecked
- {
- il.Demit(OpCodes.Ldc_I8, (long)resultVal.UInt64Value);
- }
- break;
- case TypeCode.Single:
- il.Demit(OpCodes.Ldc_R4, resultVal.SingleValue);
- break;
- case TypeCode.Double:
- il.Demit(OpCodes.Ldc_R8, resultVal.DoubleValue);
- break;
- default: Interpreter.UnreachableCase(typeCode); break;
- }
- break;
- }
- return true;
- }
- catch
- {
- // ignore exception and return the false and rethrow the exception in the invocation time
- return false;
- }
- }
- }
-
- /// Interpreter
- public static class Interpreter
- {
- /// Always returns true
- public static readonly Func TrueFunc = static () => true;
- /// Always returns false
- public static readonly Func FalseFunc = static () => false;
-
- /// Return value should be ignored
- [MethodImpl(MethodImplOptions.NoInlining)]
- internal static void UnreachableCase(T @case,
- [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1)
- {
-#if INTERPRETATION_DIAGNOSTICS
- Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}");
- Debugger.Break();
-#endif
- throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}");
- }
-
- /// Return value should be ignored
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static R UnreachableCase(T @case, R result,
- [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1)
- {
-#if INTERPRETATION_DIAGNOSTICS
- Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}");
- Debugger.Break();
-#endif
- throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}");
- }
-
- /// Operation accepting IComparable inputs and producing bool output
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsComparison(ExpressionType nodeType) =>
- nodeType == ExpressionType.Equal |
- nodeType == ExpressionType.NotEqual |
- nodeType == ExpressionType.GreaterThan |
- nodeType == ExpressionType.GreaterThanOrEqual |
- nodeType == ExpressionType.LessThan |
- nodeType == ExpressionType.LessThanOrEqual;
-
- /// Operation accepting the same primitive type inputs (or of the coalescing types) and producing the "same" primitive type output
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsArithmeticBinary(ExpressionType nodeType) =>
- nodeType == ExpressionType.Add |
- nodeType == ExpressionType.Subtract |
- nodeType == ExpressionType.Multiply |
- nodeType == ExpressionType.Divide |
- nodeType == ExpressionType.Modulo |
- nodeType == ExpressionType.Power |
- nodeType == ExpressionType.LeftShift |
- nodeType == ExpressionType.RightShift |
- nodeType == ExpressionType.And |
- nodeType == ExpressionType.Or |
- nodeType == ExpressionType.ExclusiveOr;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static void NegatePrimitiveValue(ref PValue value, TypeCode code)
- {
- switch (code)
- {
- case TypeCode.Char: value.CharValue = (char)-value.CharValue; break;
- case TypeCode.SByte: value.SByteValue = (sbyte)-value.SByteValue; break;
- case TypeCode.Byte: value.Int16Value = (short)-value.ByteValue; break;
- case TypeCode.Int16: value.Int16Value = (short)-value.Int16Value; break;
- case TypeCode.UInt16: value.Int32Value = (int)-value.UInt16Value; break;
- case TypeCode.Int32: value.Int32Value = -value.Int32Value; break;
- // Negate can not be applied to the UInt32
- case TypeCode.UInt32: UnreachableCase(code); break;
- case TypeCode.Single: value.SingleValue = -value.SingleValue; break;
- case TypeCode.Double: value.DoubleValue = -value.DoubleValue; break;
- default: UnreachableCase(code); break;
- }
- }
-
- internal static void ConvertPrimitiveValueFromTo(ref PValue value, TypeCode fromCode, TypeCode toCode)
- {
- switch (toCode)
- {
- case TypeCode.SByte:
- switch (fromCode)
- {
- case TypeCode.Char: break;
- case TypeCode.SByte: break;
- case TypeCode.Byte: value.SByteValue = (sbyte)value.ByteValue; break;
- case TypeCode.Int16: value.SByteValue = (sbyte)value.Int16Value; break;
- case TypeCode.UInt16: value.SByteValue = (sbyte)value.UInt16Value; break;
- case TypeCode.Int32: value.SByteValue = (sbyte)value.Int32Value; break;
- case TypeCode.UInt32: value.SByteValue = (sbyte)value.UInt32Value; break;
- case TypeCode.Int64: value.SByteValue = (sbyte)value.Int64Value; break;
- case TypeCode.UInt64: value.SByteValue = (sbyte)value.UInt64Value; break;
- case TypeCode.Single: value.SByteValue = (sbyte)value.SingleValue; break;
- case TypeCode.Double: value.SByteValue = (sbyte)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.Byte:
- switch (fromCode)
- {
- case TypeCode.Char: value.ByteValue = (byte)value.CharValue; break;
- case TypeCode.SByte: value.ByteValue = (byte)value.SByteValue; break;
- case TypeCode.Byte: break;
- case TypeCode.Int16: value.ByteValue = (byte)value.Int16Value; break;
- case TypeCode.UInt16: value.ByteValue = (byte)value.UInt16Value; break;
- case TypeCode.Int32: value.ByteValue = (byte)value.Int32Value; break;
- case TypeCode.UInt32: value.ByteValue = (byte)value.UInt32Value; break;
- case TypeCode.Int64: value.ByteValue = (byte)value.Int64Value; break;
- case TypeCode.UInt64: value.ByteValue = (byte)value.UInt64Value; break;
- case TypeCode.Single: value.ByteValue = (byte)value.SingleValue; break;
- case TypeCode.Double: value.ByteValue = (byte)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.Int16:
- switch (fromCode)
- {
- case TypeCode.Char: value.Int16Value = (short)value.CharValue; break;
- case TypeCode.SByte: value.Int16Value = (short)value.SByteValue; break;
- case TypeCode.Byte: value.Int16Value = (short)value.ByteValue; break;
- case TypeCode.Int16: break;
- case TypeCode.UInt16: value.Int16Value = (short)value.UInt16Value; break;
- case TypeCode.Int32: value.Int16Value = (short)value.Int32Value; break;
- case TypeCode.UInt32: value.Int16Value = (short)value.UInt32Value; break;
- case TypeCode.Int64: value.Int16Value = (short)value.Int64Value; break;
- case TypeCode.UInt64: value.Int16Value = (short)value.UInt64Value; break;
- case TypeCode.Single: value.Int16Value = (short)value.SingleValue; break;
- case TypeCode.Double: value.Int16Value = (short)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.UInt16:
- switch (fromCode)
- {
- case TypeCode.Char: value.UInt16Value = (ushort)value.CharValue; break;
- case TypeCode.SByte: value.UInt16Value = (ushort)value.SByteValue; break;
- case TypeCode.Byte: value.UInt16Value = (ushort)value.ByteValue; break;
- case TypeCode.Int16: value.UInt16Value = (ushort)value.Int16Value; break;
- case TypeCode.UInt16: break;
- case TypeCode.Int32: value.UInt16Value = (ushort)value.Int32Value; break;
- case TypeCode.UInt32: value.UInt16Value = (ushort)value.UInt32Value; break;
- case TypeCode.Int64: value.UInt16Value = (ushort)value.Int64Value; break;
- case TypeCode.UInt64: value.UInt16Value = (ushort)value.UInt64Value; break;
- case TypeCode.Single: value.UInt16Value = (ushort)value.SingleValue; break;
- case TypeCode.Double: value.UInt16Value = (ushort)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.Int32:
- switch (fromCode)
- {
- case TypeCode.Char: value.Int32Value = (int)value.CharValue; break;
- case TypeCode.SByte: value.Int32Value = (int)value.SByteValue; break;
- case TypeCode.Byte: value.Int32Value = (int)value.ByteValue; break;
- case TypeCode.Int16: value.Int32Value = (int)value.Int16Value; break;
- case TypeCode.UInt16: value.Int32Value = (int)value.UInt16Value; break;
- case TypeCode.Int32: break;
- case TypeCode.UInt32: value.Int32Value = (int)value.UInt32Value; break;
- case TypeCode.Int64: value.Int32Value = (int)value.Int64Value; break;
- case TypeCode.UInt64: value.Int32Value = (int)value.UInt64Value; break;
- case TypeCode.Single: value.Int32Value = (int)value.SingleValue; break;
- case TypeCode.Double: value.Int32Value = (int)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.UInt32:
- switch (fromCode)
- {
- case TypeCode.Char: value.UInt32Value = (uint)value.CharValue; break;
- case TypeCode.SByte: value.UInt32Value = (uint)value.SByteValue; break;
- case TypeCode.Byte: value.UInt32Value = (uint)value.ByteValue; break;
- case TypeCode.Int16: value.UInt32Value = (uint)value.Int16Value; break;
- case TypeCode.UInt16: value.UInt32Value = (uint)value.UInt16Value; break;
- case TypeCode.Int32: value.UInt32Value = (uint)value.Int32Value; break;
- case TypeCode.UInt32: break;
- case TypeCode.Int64: value.UInt32Value = (uint)value.Int64Value; break;
- case TypeCode.UInt64: value.UInt32Value = (uint)value.UInt64Value; break;
- case TypeCode.Single: value.UInt32Value = (uint)value.SingleValue; break;
- case TypeCode.Double: value.UInt32Value = (uint)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.Int64:
- switch (fromCode)
- {
- case TypeCode.Char: value.Int64Value = (long)value.CharValue; break;
- case TypeCode.SByte: value.Int64Value = (long)value.SByteValue; break;
- case TypeCode.Byte: value.Int64Value = (long)value.ByteValue; break;
- case TypeCode.Int16: value.Int64Value = (long)value.Int16Value; break;
- case TypeCode.UInt16: value.Int64Value = (long)value.UInt16Value; break;
- case TypeCode.Int32: value.Int64Value = (long)value.Int32Value; break;
- case TypeCode.UInt32: value.Int64Value = (long)value.UInt32Value; break;
- case TypeCode.Int64: break;
- case TypeCode.UInt64: value.Int64Value = (long)value.UInt64Value; break;
- case TypeCode.Single: value.Int64Value = (long)value.SingleValue; break;
- case TypeCode.Double: value.Int64Value = (long)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.UInt64:
- switch (fromCode)
- {
- case TypeCode.Char: value.UInt64Value = (ulong)value.CharValue; break;
- case TypeCode.SByte: value.UInt64Value = (ulong)value.SByteValue; break;
- case TypeCode.Byte: value.UInt64Value = (ulong)value.ByteValue; break;
- case TypeCode.Int16: value.UInt64Value = (ulong)value.Int16Value; break;
- case TypeCode.UInt16: value.UInt64Value = (ulong)value.UInt16Value; break;
- case TypeCode.Int32: value.UInt64Value = (ulong)value.Int32Value; break;
- case TypeCode.UInt32: value.UInt64Value = (ulong)value.UInt32Value; break;
- case TypeCode.Int64: value.UInt64Value = (ulong)value.Int64Value; break;
- case TypeCode.UInt64: break;
- case TypeCode.Single: value.UInt64Value = (ulong)value.SingleValue; break;
- case TypeCode.Double: value.UInt64Value = (ulong)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.Single:
- switch (fromCode)
- {
- case TypeCode.Char: value.SingleValue = (float)value.CharValue; break;
- case TypeCode.SByte: value.SingleValue = (float)value.SByteValue; break;
- case TypeCode.Byte: value.SingleValue = (float)value.ByteValue; break;
- case TypeCode.Int16: value.SingleValue = (float)value.Int16Value; break;
- case TypeCode.UInt16: value.SingleValue = (float)value.UInt16Value; break;
- case TypeCode.Int32: value.SingleValue = (float)value.Int32Value; break;
- case TypeCode.UInt32: value.SingleValue = (float)value.UInt32Value; break;
- case TypeCode.Int64: value.SingleValue = (float)value.Int64Value; break;
- case TypeCode.UInt64: value.SingleValue = (float)value.UInt64Value; break;
- case TypeCode.Single: break;
- case TypeCode.Double: value.SingleValue = (float)value.DoubleValue; break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- case TypeCode.Double:
- switch (fromCode)
- {
- case TypeCode.Char: value.DoubleValue = (double)value.CharValue; break;
- case TypeCode.SByte: value.DoubleValue = (double)value.SByteValue; break;
- case TypeCode.Byte: value.DoubleValue = (double)value.ByteValue; break;
- case TypeCode.Int16: value.DoubleValue = (double)value.Int16Value; break;
- case TypeCode.UInt16: value.DoubleValue = (double)value.UInt16Value; break;
- case TypeCode.Int32: value.DoubleValue = (double)value.Int32Value; break;
- case TypeCode.UInt32: value.DoubleValue = (double)value.UInt32Value; break;
- case TypeCode.Int64: value.DoubleValue = (double)value.Int64Value; break;
- case TypeCode.UInt64: value.DoubleValue = (double)value.UInt64Value; break;
- case TypeCode.Single: value.DoubleValue = (double)value.SingleValue; break;
- case TypeCode.Double: break;
- default: UnreachableCase(fromCode); break;
- }
- break;
- }
- }
-
- [DebuggerDisplay("{Code}")]
- [StructLayout(LayoutKind.Explicit)]
- internal struct PValue
- {
- [FieldOffset(0)]
- public char CharValue;
- [FieldOffset(0)]
- public sbyte SByteValue;
- [FieldOffset(0)]
- public byte ByteValue;
- [FieldOffset(0)]
- public short Int16Value;
- [FieldOffset(0)]
- public ushort UInt16Value;
- [FieldOffset(0)]
- public int Int32Value;
- [FieldOffset(0)]
- public uint UInt32Value;
- [FieldOffset(0)]
- public long Int64Value;
- [FieldOffset(0)]
- public ulong UInt64Value;
- [FieldOffset(0)]
- public float SingleValue;
- [FieldOffset(0)]
- public double DoubleValue;
- public PValue(char value) => CharValue = value;
- public PValue(sbyte value) => SByteValue = value;
- public PValue(byte value) => ByteValue = value;
- public PValue(short value) => Int16Value = value;
- public PValue(ushort value) => UInt16Value = value;
- public PValue(int value) => Int32Value = value;
- public PValue(uint value) => UInt32Value = value;
- public PValue(long value) => Int64Value = value;
- public PValue(ulong value) => UInt64Value = value;
- public PValue(float value) => SingleValue = value;
- public PValue(double value) => DoubleValue = value;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool TryUnboxToPrimitiveValue(ref PValue value, object boxedValue, TypeCode code)
- {
- switch (code)
- {
- case TypeCode.Char: value.CharValue = (char)boxedValue; break;
- case TypeCode.SByte: value.SByteValue = (sbyte)boxedValue; break;
- case TypeCode.Byte: value.ByteValue = (byte)boxedValue; break;
- case TypeCode.Int16: value.Int16Value = (short)boxedValue; break;
- case TypeCode.UInt16: value.UInt16Value = (ushort)boxedValue; break;
- case TypeCode.Int32: value.Int32Value = (int)boxedValue; break;
- case TypeCode.UInt32: value.UInt32Value = (uint)boxedValue; break;
- case TypeCode.Int64: value.Int64Value = (long)boxedValue; break;
- case TypeCode.UInt64: value.UInt64Value = (ulong)boxedValue; break;
- case TypeCode.Single: value.SingleValue = (float)boxedValue; break;
- case TypeCode.Double: value.DoubleValue = (double)boxedValue; break;
- default: return false;
- }
- return true;
- }
-
- // todo: @perf think how to avoid this boxing thing altogether, maybe do not expose it at all to force client to handle the union values
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static object BoxPrimitiveValue(ref PValue value, TypeCode code) => code switch
- {
- TypeCode.Char => value.CharValue,
- TypeCode.SByte => value.SByteValue,
- TypeCode.Byte => value.ByteValue,
- TypeCode.Int16 => value.Int16Value,
- TypeCode.UInt16 => value.UInt16Value,
- TypeCode.Int32 => value.Int32Value,
- TypeCode.UInt32 => value.UInt32Value,
- TypeCode.Int64 => value.Int64Value,
- TypeCode.UInt64 => value.UInt64Value,
- TypeCode.Single => value.SingleValue,
- TypeCode.Double => value.DoubleValue,
- _ => UnreachableCase(code, (object)null)
- };
-
- /// Converts an integer/enum/char boxed value to long for uniform comparison.
- [MethodImpl((MethodImplOptions)256)]
- internal static long ConvertValueObjectToLong(object valObj)
- {
- Debug.Assert(valObj != null);
- var type = valObj.GetType();
- type = type.IsEnum ? Enum.GetUnderlyingType(type) : type;
- return Type.GetTypeCode(type) switch
- {
- TypeCode.Char => (long)(char)valObj,
- TypeCode.SByte => (long)(sbyte)valObj,
- TypeCode.Byte => (long)(byte)valObj,
- TypeCode.Int16 => (long)(short)valObj,
- TypeCode.UInt16 => (long)(ushort)valObj,
- TypeCode.Int32 => (long)(int)valObj,
- TypeCode.UInt32 => (long)(uint)valObj,
- TypeCode.Int64 => (long)valObj,
- TypeCode.UInt64 => (long)(ulong)valObj,
- _ => 0 // unreachable
- };
- }
-
- internal static bool ComparePrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType)
- {
- switch (nodeType)
- {
- case ExpressionType.GreaterThan:
- return code switch
- {
- TypeCode.Char => left.CharValue > right.CharValue,
- TypeCode.SByte => left.SByteValue > right.SByteValue,
- TypeCode.Byte => left.ByteValue > right.ByteValue,
- TypeCode.Int16 => left.Int16Value > right.Int16Value,
- TypeCode.UInt16 => left.UInt16Value > right.UInt16Value,
- TypeCode.Int32 => left.Int32Value > right.Int32Value,
- TypeCode.UInt32 => left.UInt32Value > right.UInt32Value,
- TypeCode.Int64 => left.Int64Value > right.Int64Value,
- TypeCode.UInt64 => left.UInt64Value > right.UInt64Value,
- TypeCode.Single => left.SingleValue > right.SingleValue,
- TypeCode.Double => left.DoubleValue > right.DoubleValue,
- _ => UnreachableCase(code, false)
- };
- case ExpressionType.GreaterThanOrEqual:
- return code switch
- {
- TypeCode.Char => left.CharValue >= right.CharValue,
- TypeCode.SByte => left.SByteValue >= right.SByteValue,
- TypeCode.Byte => left.ByteValue >= right.ByteValue,
- TypeCode.Int16 => left.Int16Value >= right.Int16Value,
- TypeCode.UInt16 => left.UInt16Value >= right.UInt16Value,
- TypeCode.Int32 => left.Int32Value >= right.Int32Value,
- TypeCode.UInt32 => left.UInt32Value >= right.UInt32Value,
- TypeCode.Int64 => left.Int64Value >= right.Int64Value,
- TypeCode.UInt64 => left.UInt64Value >= right.UInt64Value,
- TypeCode.Single => left.SingleValue >= right.SingleValue,
- TypeCode.Double => left.DoubleValue >= right.DoubleValue,
- _ => UnreachableCase(code, false)
- };
- case ExpressionType.LessThan:
- return code switch
- {
- TypeCode.Char => left.CharValue < right.CharValue,
- TypeCode.SByte => left.SByteValue < right.SByteValue,
- TypeCode.Byte => left.ByteValue < right.ByteValue,
- TypeCode.Int16 => left.Int16Value < right.Int16Value,
- TypeCode.UInt16 => left.UInt16Value < right.UInt16Value,
- TypeCode.Int32 => left.Int32Value < right.Int32Value,
- TypeCode.UInt32 => left.UInt32Value < right.UInt32Value,
- TypeCode.Int64 => left.Int64Value < right.Int64Value,
- TypeCode.UInt64 => left.UInt64Value < right.UInt64Value,
- TypeCode.Single => left.SingleValue < right.SingleValue,
- TypeCode.Double => left.DoubleValue < right.DoubleValue,
- _ => UnreachableCase(code, false)
- };
- case ExpressionType.LessThanOrEqual:
- return code switch
- {
- TypeCode.Char => left.CharValue <= right.CharValue,
- TypeCode.SByte => left.SByteValue <= right.SByteValue,
- TypeCode.Byte => left.ByteValue <= right.ByteValue,
- TypeCode.Int16 => left.Int16Value <= right.Int16Value,
- TypeCode.UInt16 => left.UInt16Value <= right.UInt16Value,
- TypeCode.Int32 => left.Int32Value <= right.Int32Value,
- TypeCode.UInt32 => left.UInt32Value <= right.UInt32Value,
- TypeCode.Int64 => left.Int64Value <= right.Int64Value,
- TypeCode.UInt64 => left.UInt64Value <= right.UInt64Value,
- TypeCode.Single => left.SingleValue <= right.SingleValue,
- TypeCode.Double => left.DoubleValue <= right.DoubleValue,
- _ => UnreachableCase(code, false)
- };
- default: return UnreachableCase(nodeType, false);
- }
- }
-
- /// Puts the result to the `left` para meter
- internal static void DoArithmeticForPrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType)
- {
- switch (nodeType)
- {
- case ExpressionType.Add:
- switch (code)
- {
- case TypeCode.Char: left.CharValue += right.CharValue; break;
- // System Expression does not define the Add for sbyte and byte, but let's keep it here because it is allowed in C# and LightExpression
- case TypeCode.SByte: left.SByteValue += right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue += right.ByteValue; break;
- // the rest
- case TypeCode.Int16: left.Int16Value += right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value += right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value += right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value += right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value += right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value += right.UInt64Value; break;
- case TypeCode.Single: left.SingleValue += right.SingleValue; break;
- case TypeCode.Double: left.DoubleValue += right.DoubleValue; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.Subtract:
- switch (code)
- {
- case TypeCode.Char: left.CharValue -= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue -= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue -= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value -= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value -= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value -= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value -= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value -= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value -= right.UInt64Value; break;
- case TypeCode.Single: left.SingleValue -= right.SingleValue; break;
- case TypeCode.Double: left.DoubleValue -= right.DoubleValue; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.Multiply:
- switch (code)
- {
- case TypeCode.Char: left.CharValue *= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue *= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue *= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value *= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value *= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value *= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value *= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value *= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value *= right.UInt64Value; break;
- case TypeCode.Single: left.SingleValue *= right.SingleValue; break;
- case TypeCode.Double: left.DoubleValue *= right.DoubleValue; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.Divide:
- switch (code)
- {
- case TypeCode.Char: left.CharValue /= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue /= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue /= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value /= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value /= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value /= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value /= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value /= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value /= right.UInt64Value; break;
- case TypeCode.Single: left.SingleValue /= right.SingleValue; break;
- case TypeCode.Double: left.DoubleValue /= right.DoubleValue; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.Modulo:
- switch (code)
- {
- case TypeCode.Char: left.CharValue %= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue %= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue %= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value %= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value %= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value %= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value %= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value %= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value %= right.UInt64Value; break;
- case TypeCode.Single: left.SingleValue %= right.SingleValue; break;
- case TypeCode.Double: left.DoubleValue %= right.DoubleValue; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.And:
- switch (code)
- {
- case TypeCode.Char: left.CharValue &= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue &= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue &= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value &= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value &= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value &= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value &= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value &= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value &= right.UInt64Value; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.Or:
- switch (code)
- {
- case TypeCode.Char: left.CharValue |= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue |= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue |= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value |= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value |= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value |= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value |= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value |= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value |= right.UInt64Value; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.ExclusiveOr:
- switch (code)
- {
- case TypeCode.Char: left.CharValue ^= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue ^= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue ^= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value ^= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value ^= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value ^= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value ^= right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value ^= right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value ^= right.UInt64Value; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.LeftShift:
- switch (code)
- {
- case TypeCode.Char: left.CharValue <<= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue <<= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue <<= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value <<= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value <<= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value <<= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value <<= (int)right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value <<= (int)right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value <<= (int)right.UInt64Value; break;
- default: UnreachableCase(code); break;
- }
- break;
- case ExpressionType.RightShift:
- switch (code)
- {
- case TypeCode.Char: left.CharValue >>= right.CharValue; break;
- case TypeCode.SByte: left.SByteValue >>= right.SByteValue; break;
- case TypeCode.Byte: left.ByteValue >>= right.ByteValue; break;
- case TypeCode.Int16: left.Int16Value >>= right.Int16Value; break;
- case TypeCode.UInt16: left.UInt16Value >>= right.UInt16Value; break;
- case TypeCode.Int32: left.Int32Value >>= right.Int32Value; break;
- case TypeCode.UInt32: left.UInt32Value >>= (int)right.UInt32Value; break;
- case TypeCode.Int64: left.Int64Value >>= (int)right.Int64Value; break;
- case TypeCode.UInt64: left.UInt64Value >>= (int)right.UInt64Value; break;
- default: UnreachableCase(code); break;
- }
- break;
- default: UnreachableCase(nodeType); break;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool TrySetPrimitiveValueToDefault(ref PValue value, TypeCode code)
- {
- switch (code)
- {
- case TypeCode.Char: value.CharValue = default; break;
- case TypeCode.SByte: value.SByteValue = default; break;
- case TypeCode.Byte: value.ByteValue = default; break;
- case TypeCode.Int16: value.Int16Value = default; break;
- case TypeCode.UInt16: value.UInt16Value = default; break;
- case TypeCode.Int32: value.Int32Value = default; break;
- case TypeCode.UInt32: value.UInt32Value = default; break;
- case TypeCode.Int64: value.Int64Value = default; break;
- case TypeCode.UInt64: value.UInt64Value = default; break;
- case TypeCode.Single: value.SingleValue = default; break;
- case TypeCode.Double: value.DoubleValue = default; break;
- default: return false;
- }
- return true;
- }
-
- /// Fast, mostly negative check to skip or proceed with interpretation.
- /// Depending on the context you may avoid calling it because you know the interpreted expression beforehand,
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsCandidateForInterpretation(Expression expr)
- {
- var nodeType = expr.NodeType;
- return
- nodeType == ExpressionType.Constant |
- nodeType == ExpressionType.Default |
- nodeType == ExpressionType.Convert |
- nodeType == ExpressionType.Not |
- nodeType == ExpressionType.Negate |
- expr is BinaryExpression;
- // todo: @wip include conditional?
- }
-
-#if INTERPRETATION_DIAGNOSTICS
-
- [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")]
- [UnconditionalSuppressMessage("Trimming", "IL2075:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")]
- private static void CollectCallingTestName()
- {
- var stackTrace = new StackTrace();
- var frames = stackTrace.GetFrames();
-
- // Skip this method and its immediate caller, and start from the outer callers
- var found = false;
- for (int i = 3; i < frames.Length; ++i)
- {
- var frame = frames[i];
- var method = frame.GetMethod();
- var type = method?.DeclaringType;
- if (type == null)
- continue;
-
- var ifaces = type.GetInterfaces();
- if (ifaces.Length == 0)
- continue;
-
- foreach (var iface in ifaces)
- if (iface.Name.Contains("Test"))
- {
- found = true;
- break;
- }
-
- if (found)
- {
- found = true;
- Console.WriteLine($"//Interpretation in: {type.Name}.{method.Name}");
- break; // collect the first found thing in stack trace
- }
- }
-
- if (!found)
- {
- var methodTrace = string.Join("; ", frames.Skip(3).Select(f => f.GetMethod().Name).ToArray());
- Console.WriteLine($"//Interpretation in: not found in stack trace: {methodTrace}");
- }
- }
-#endif
-
- /// Wraps `TryInterpretPrimitive` in the try catch block.
- /// In case of exception FEC will emit the whole computation to throw exception in the invocation phase
- public static bool TryInterpretBool(out bool result, Expression expr, CompilerFlags flags)
- {
- var exprType = expr.Type;
- Debug.Assert(exprType.IsPrimitive, // todo: @feat nullables are not supported yet // || Nullable.GetUnderlyingType(exprType)?.IsPrimitive == true,
- "Can only reduce the boolean for the expressions of primitive type but found " + expr.Type);
- result = false;
- if ((flags & CompilerFlags.DisableInterpreter) != 0)
- return false;
- try
- {
- var ok = TryInterpretBool(ref result, expr, expr.NodeType);
-#if INTERPRETATION_DIAGNOSTICS
- if (ok) CollectCallingTestName();
-#endif
- return ok;
- }
- catch
- {
- // ignore exception and return the false and rethrow the exception in the invocation time
- return false;
- }
- }
-
- ///
- /// Tries to determine at compile time which branch a switch expression will take.
- /// Works for integer/enum and string switch values with no custom equality method.
- /// Returns true when the switch value is deterministic; is set to
- /// the branch body to emit (null means use default body which may itself be null).
- ///
- public static bool TryFindSwitchBranch(SwitchExpression switchExpr, CompilerFlags flags, out Expression matchedBody)
- {
- matchedBody = null;
- if (switchExpr.Comparison != null) return false; // custom equality: can't interpret statically
- if ((flags & CompilerFlags.DisableInterpreter) != 0) return false;
- var switchValueExpr = switchExpr.SwitchValue;
- var switchValueType = switchValueExpr.Type;
- var cases = switchExpr.Cases;
- try
- {
- // String switch: only constant switch values supported
- if (switchValueType == typeof(string))
- {
- if (switchValueExpr is not ConstantExpression ce) return false;
- var switchStr = ce.Value;
- for (var i = 0; i < cases.Count; i++)
- {
- var testValues = cases[i].TestValues;
- for (var j = 0; j < testValues.Count; j++)
- {
- if (testValues[j] is not ConstantExpression testConst) return false;
- if (Equals(switchStr, testConst.Value)) { matchedBody = cases[i].Body; return true; }
- }
- }
- matchedBody = switchExpr.DefaultBody;
- return true;
- }
-
- // Integer / enum / char switch
- var effectiveType = switchValueType.IsEnum ? Enum.GetUnderlyingType(switchValueType) : switchValueType;
- var typeCode = Type.GetTypeCode(effectiveType);
- if (typeCode < TypeCode.Char || typeCode > TypeCode.UInt64) return false; // non-integral (e.g. float, decimal)
-
- long switchValLong;
- if (switchValueExpr is ConstantExpression switchConst && switchConst.Value != null)
- switchValLong = ConvertValueObjectToLong(switchConst.Value);
- else if (typeCode == TypeCode.Int32)
- {
- var intVal = 0;
- if (!TryInterpretInt(ref intVal, switchValueExpr, switchValueExpr.NodeType)) return false;
- switchValLong = intVal;
- }
- else
- {
- PValue pv = default;
- if (!TryInterpretPrimitiveValue(ref pv, switchValueExpr, typeCode, switchValueExpr.NodeType)) return false;
- switchValLong = PValueToLong(ref pv, typeCode);
- }
-
- for (var i = 0; i < cases.Count; i++)
- {
- var testValues = cases[i].TestValues;
- for (var j = 0; j < testValues.Count; j++)
- {
- if (testValues[j] is not ConstantExpression testConst || testConst.Value == null) continue;
- if (switchValLong == ConvertValueObjectToLong(testConst.Value)) { matchedBody = cases[i].Body; return true; }
- }
- }
- matchedBody = switchExpr.DefaultBody;
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- /// Converts a union to a long for integer/char comparison.
- [MethodImpl((MethodImplOptions)256)]
- internal static long PValueToLong(ref PValue value, TypeCode code) => code switch
- {
- TypeCode.Char => (long)value.CharValue,
- TypeCode.SByte => (long)value.SByteValue,
- TypeCode.Byte => (long)value.ByteValue,
- TypeCode.Int16 => (long)value.Int16Value,
- TypeCode.UInt16 => (long)value.UInt16Value,
- TypeCode.Int32 => (long)value.Int32Value,
- TypeCode.UInt32 => (long)value.UInt32Value,
- TypeCode.Int64 => value.Int64Value,
- TypeCode.UInt64 => (long)value.UInt64Value,
- _ => 0L,
- };
-
- // todo: @perf try split to `TryInterpretBinary` overload to streamline the calls for TryEmitConditional and similar
- /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic.
- internal static bool TryInterpretBool(ref bool resultBool, Expression expr, ExpressionType nodeType)
- {
- // what operations are supported, to get the boolean result?
- // yes: not, logical, comparison, default
- // not: negate, arithmetic, convert to bool
- if (nodeType == ExpressionType.Not)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- if (operandExpr.NodeType == ExpressionType.Conditional)
- operandExpr = Tools.TryReduceConditional((ConditionalExpression)operandExpr);
- if (operandExpr is ConstantExpression co)
- resultBool = (bool)co.Value;
- else if (!TryInterpretBool(ref resultBool, operandExpr, operandExpr.NodeType))
- return false;
-
- resultBool = !resultBool;
- return true;
- }
-
- if (nodeType == ExpressionType.AndAlso |
- nodeType == ExpressionType.OrElse)
- {
- var binaryExpr = (BinaryExpression)expr;
-
- // Interpreting the left part as the first candidate for the result
- var left = binaryExpr.Left;
- if (left.NodeType == ExpressionType.Conditional)
- left = Tools.TryReduceConditional((ConditionalExpression)left);
- if (left is ConstantExpression lc)
- resultBool = (bool)lc.Value;
- else if (!TryInterpretBool(ref resultBool, left, left.NodeType))
- return false;
-
- // Short circuit the interpretation, because this is an actual logic of these logical operations
- if (resultBool & nodeType == ExpressionType.OrElse ||
- !resultBool & nodeType == ExpressionType.AndAlso)
- return true;
-
- // If the first part is not enough to decide of the expression result, go right
- var right = binaryExpr.Right;
- if (right.NodeType == ExpressionType.Conditional)
- right = Tools.TryReduceConditional((ConditionalExpression)right);
- if (right is ConstantExpression rc)
- {
- resultBool = (bool)rc.Value;
- return true;
- }
- return TryInterpretBool(ref resultBool, right, right.NodeType);
- }
-
- if (nodeType == ExpressionType.Equal |
- nodeType == ExpressionType.NotEqual)
- {
- var binaryExpr = (BinaryExpression)expr;
- var right = binaryExpr.Right;
- var left = binaryExpr.Left;
- var leftCode = Type.GetTypeCode(left.Type);
- if (leftCode == TypeCode.Boolean)
- {
- var leftBool = false;
- if (left is ConstantExpression lc)
- leftBool = (bool)lc.Value;
- else if (!TryInterpretBool(ref leftBool, left, left.NodeType))
- return false;
-
- var rightBool = false;
- if (right is ConstantExpression rc)
- rightBool = (bool)rc.Value;
- else if (!TryInterpretBool(ref rightBool, right, right.NodeType))
- return false;
-
- resultBool = nodeType == ExpressionType.Equal ? leftBool == rightBool : leftBool != rightBool;
- return true;
- }
- if (leftCode == TypeCode.Int32)
- {
- var leftInt = 0;
- if (left is ConstantExpression lc)
- leftInt = (int)lc.Value;
- else if (!TryInterpretInt(ref leftInt, left, left.NodeType))
- return false;
-
- var rightInt = 0;
- if (right is ConstantExpression rc)
- rightInt = (int)rc.Value;
- else if (!TryInterpretInt(ref rightInt, right, right.NodeType))
- return false;
-
- resultBool = nodeType == ExpressionType.Equal ? leftInt == rightInt : leftInt != rightInt;
- return true;
- }
- if (leftCode == TypeCode.Decimal)
- {
- decimal decimalLeft = default;
- if (left is ConstantExpression lc)
- decimalLeft = (decimal)lc.Value;
- else if (!TryInterpretDecimal(ref decimalLeft, left, left.NodeType))
- return false;
-
- decimal rightDec = default;
- if (right is ConstantExpression rc)
- rightDec = (decimal)rc.Value;
- else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType))
- return false;
-
- resultBool = nodeType == ExpressionType.Equal ? decimalLeft == rightDec : decimalLeft != rightDec;
- return true;
- }
- // not a bool, int, or decimal
- {
- PValue leftVal = default;
- if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) ||
- !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType))
- return false;
-
- PValue rightVal = default;
- if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) ||
- !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType))
- return false;
-
- resultBool = leftCode switch
- {
- TypeCode.Char => leftVal.CharValue == rightVal.CharValue,
- TypeCode.SByte => leftVal.SByteValue == rightVal.SByteValue,
- TypeCode.Byte => leftVal.ByteValue == rightVal.ByteValue,
- TypeCode.Int16 => leftVal.Int16Value == rightVal.Int16Value,
- TypeCode.UInt16 => leftVal.UInt16Value == rightVal.UInt16Value,
- TypeCode.Int32 => leftVal.Int32Value == rightVal.Int32Value,
- TypeCode.UInt32 => leftVal.UInt32Value == rightVal.UInt32Value,
- TypeCode.Int64 => leftVal.Int64Value == rightVal.Int64Value,
- TypeCode.UInt64 => leftVal.UInt64Value == rightVal.UInt64Value,
- TypeCode.Single => leftVal.SingleValue == rightVal.SingleValue,
- TypeCode.Double => leftVal.DoubleValue == rightVal.DoubleValue,
- _ => UnreachableCase(leftCode, false),
- };
- resultBool = nodeType == ExpressionType.Equal ? resultBool : !resultBool;
- return true;
- }
- }
-
- if (nodeType == ExpressionType.GreaterThan |
- nodeType == ExpressionType.GreaterThanOrEqual |
- nodeType == ExpressionType.LessThan |
- nodeType == ExpressionType.LessThanOrEqual)
- {
- var binaryExpr = (BinaryExpression)expr;
- var left = binaryExpr.Left;
- var right = binaryExpr.Right;
- var leftCode = Type.GetTypeCode(left.Type);
- Debug.Assert(leftCode != TypeCode.Boolean, "Boolean values are not comparable by less or greater");
-
- if (leftCode == TypeCode.Int32)
- {
- int intLeft = 0;
- if (left is ConstantExpression lc)
- intLeft = (int)lc.Value;
- else if (!TryInterpretInt(ref intLeft, left, left.NodeType))
- return false;
-
- int rightInt = 0;
- if (right is ConstantExpression rc)
- rightInt = (int)rc.Value;
- else if (!TryInterpretInt(ref rightInt, right, right.NodeType))
- return false;
-
- resultBool = nodeType switch
- {
- ExpressionType.GreaterThan => intLeft > rightInt,
- ExpressionType.GreaterThanOrEqual => intLeft >= rightInt,
- ExpressionType.LessThan => intLeft < rightInt,
- ExpressionType.LessThanOrEqual => intLeft <= rightInt,
- _ => UnreachableCase(nodeType, false),
- };
- return true;
- }
- if (leftCode == TypeCode.Decimal)
- {
- decimal leftDec = default;
- if (left is ConstantExpression lc)
- leftDec = (decimal)lc.Value;
- else if (!TryInterpretDecimal(ref leftDec, left, left.NodeType))
- return false;
-
- decimal rightDec = default;
- if (right is ConstantExpression rc)
- rightDec = (decimal)rc.Value;
- else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType))
- return false;
-
- resultBool = nodeType switch
- {
- ExpressionType.GreaterThan => leftDec > rightDec,
- ExpressionType.GreaterThanOrEqual => leftDec >= rightDec,
- ExpressionType.LessThan => leftDec < rightDec,
- ExpressionType.LessThanOrEqual => leftDec <= rightDec,
- _ => UnreachableCase(nodeType, false),
- };
- return true;
- }
- // not a bool, int, or decimal
- {
- PValue leftVal = default;
- if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) ||
- !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType))
- return false;
-
- PValue rightVal = default;
- if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) ||
- !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType))
- return false;
-
- resultBool = ComparePrimitiveValues(ref leftVal, ref rightVal, leftCode, nodeType);
- return true;
- }
- }
-
- if (expr is ConstantExpression constExpr)
- {
- resultBool = (bool)constExpr.Value;
- return true;
- }
-
- if (nodeType == ExpressionType.Default)
- {
- resultBool = false;
- return true;
- }
-
- return false;
- }
-
- /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic.
- internal static bool TryInterpretDecimal(ref decimal resultDec, Expression expr, ExpressionType nodeType)
- {
- // What operations are supported, to get the decimal result:
- // yes: arithmetic, negate, default, convert
- // no: not, logical, comparison
-
- if (IsArithmeticBinary(nodeType))
- {
- var binaryExpr = (BinaryExpression)expr;
- var left = binaryExpr.Left;
- if (left is ConstantExpression lc)
- resultDec = (decimal)lc.Value;
- else if (!TryInterpretDecimal(ref resultDec, left, left.NodeType))
- return false;
-
- decimal rightDec = default;
- var right = binaryExpr.Right;
- if (right is ConstantExpression rc)
- rightDec = (decimal)rc.Value;
- else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType))
- return false;
-
- switch (nodeType)
- {
- case ExpressionType.Add: resultDec += rightDec; break;
- case ExpressionType.Subtract: resultDec -= rightDec; break;
- case ExpressionType.Multiply: resultDec *= rightDec; break;
- case ExpressionType.Divide: resultDec /= rightDec; break;
- case ExpressionType.Modulo: resultDec %= rightDec; break;
- default: UnreachableCase(nodeType); break;
- }
- return true;
- }
-
- if (nodeType == ExpressionType.Negate)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- if (operandExpr is ConstantExpression co)
- resultDec = (decimal)co.Value;
- else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType))
- return false;
-
- resultDec = -resultDec;
- return true;
- }
-
- if (expr is ConstantExpression constExpr)
- {
- resultDec = (decimal)constExpr.Value;
- return true;
- }
-
- if (nodeType == ExpressionType.Default)
- {
- resultDec = default;
- return true;
- }
-
- if (nodeType == ExpressionType.Convert)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- var operandCode = Type.GetTypeCode(operandExpr.Type);
- Debug.Assert(operandCode != TypeCode.Boolean,
- "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in UValue");
-
- PValue operandVal = default;
- if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) ||
- !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType))
- return false;
-
- resultDec = operandCode switch
- {
- TypeCode.Char => operandVal.CharValue,
- TypeCode.SByte => operandVal.SByteValue,
- TypeCode.Byte => operandVal.ByteValue,
- TypeCode.Int16 => operandVal.Int16Value,
- TypeCode.UInt16 => operandVal.UInt16Value,
- TypeCode.Int32 => operandVal.Int32Value,
- TypeCode.UInt32 => operandVal.UInt32Value,
- TypeCode.Int64 => operandVal.Int64Value,
- TypeCode.UInt64 => operandVal.UInt64Value,
- TypeCode.Single => (decimal)operandVal.SingleValue,
- TypeCode.Double => (decimal)operandVal.DoubleValue,
- _ => UnreachableCase(operandCode, default(decimal)),
- };
- return true;
- }
-
- return false;
- }
-
- /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic.
- /// Returns `false` if it failed to do so.
- internal static bool TryInterpretInt(ref int resultInt, Expression expr, ExpressionType nodeType)
- {
- // What is supported for the int result
- // yes: arithmetic, convert, default
- // no: not, logical, comparison
- if (IsArithmeticBinary(nodeType))
- {
- var binaryExpr = (BinaryExpression)expr;
- var left = binaryExpr.Left;
- if (left is ConstantExpression lc)
- resultInt = (int)lc.Value;
- else if (!TryInterpretInt(ref resultInt, left, left.NodeType))
- return false;
-
- int rightVal = 0;
- var right = binaryExpr.Right;
- if (right is ConstantExpression rc)
- rightVal = (int)rc.Value;
- else if (!TryInterpretInt(ref rightVal, right, right.NodeType))
- return false;
-
- resultInt = nodeType switch
- {
- ExpressionType.Add => resultInt + rightVal,
- ExpressionType.Subtract => resultInt - rightVal,
- ExpressionType.Multiply => resultInt * rightVal,
- ExpressionType.Divide => resultInt / rightVal,
- ExpressionType.Modulo => resultInt % rightVal,
- ExpressionType.LeftShift => resultInt << rightVal,
- ExpressionType.RightShift => resultInt >> rightVal,
- ExpressionType.And => resultInt & rightVal,
- ExpressionType.Or => resultInt | rightVal,
- ExpressionType.ExclusiveOr => resultInt ^ rightVal,
- ExpressionType.Power => (int)Math.Pow(resultInt, rightVal),
- _ => UnreachableCase(nodeType, 0),
- };
- return true;
- }
-
- if (nodeType == ExpressionType.Negate)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- if (operandExpr is ConstantExpression co)
- resultInt = (int)co.Value;
- else if (!TryInterpretInt(ref resultInt, operandExpr, operandExpr.NodeType))
- return false;
-
- resultInt = -resultInt;
- return true;
- }
-
- if (expr is ConstantExpression constExpr)
- {
- resultInt = (int)constExpr.Value;
- return true;
- }
-
- if (nodeType == ExpressionType.Default)
- {
- resultInt = 0;
- return true;
- }
-
- if (nodeType == ExpressionType.Convert)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- var operandCode = Type.GetTypeCode(operandExpr.Type);
- Debug.Assert(operandCode != TypeCode.Boolean,
- "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue");
-
- if (operandCode != TypeCode.Decimal)
- {
- PValue operandVal = default;
- if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) ||
- !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType))
- return false;
-
- resultInt = operandCode switch
- {
- TypeCode.Char => operandVal.CharValue,
- TypeCode.SByte => operandVal.SByteValue,
- TypeCode.Byte => operandVal.ByteValue,
- TypeCode.Int16 => operandVal.Int16Value,
- TypeCode.UInt16 => operandVal.UInt16Value,
- TypeCode.Int32 => operandVal.Int32Value,
- TypeCode.UInt32 => (int)operandVal.UInt32Value,
- TypeCode.Int64 => (int)operandVal.Int64Value,
- TypeCode.UInt64 => (int)operandVal.UInt64Value,
- TypeCode.Single => (int)operandVal.SingleValue,
- TypeCode.Double => (int)operandVal.DoubleValue,
- _ => UnreachableCase(operandCode, 0),
- };
- return true;
- }
- // then for the decimal
- {
- decimal resultDec = default;
- if (operandExpr is ConstantExpression co)
- resultDec = (decimal)co.Value;
- else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType))
- return false;
-
- resultInt = (int)resultDec;
- return true;
- }
- }
- return false;
- }
-
- /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic.
- /// Returns `false` if it is failed to do so.
- internal static bool TryInterpretPrimitiveValue(ref PValue result, Expression expr, TypeCode exprCode, ExpressionType nodeType)
- {
- // What is supported for the non-boolean, non-decimal result
- // yes: arithmetic, convert, default
- // no: not, logical, comparison
- if (IsArithmeticBinary(nodeType))
- {
- var binaryExpr = (BinaryExpression)expr;
- var left = binaryExpr.Left;
- var leftCode = Type.GetTypeCode(left.Type);
- if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref result, lc.Value, leftCode) ||
- !TryInterpretPrimitiveValue(ref result, left, leftCode, left.NodeType))
- return false;
-
- PValue rightVal = default;
- var right = binaryExpr.Right;
- // Using the leftCode to interpret the right part of the binary expression,
- // because for supported operations left and right types are the same
- if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) ||
- !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType))
- return false;
-
- DoArithmeticForPrimitiveValues(ref result, ref rightVal, leftCode, nodeType);
- return true;
- }
-
- if (nodeType == ExpressionType.Negate)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- var operandCode = Type.GetTypeCode(operandExpr.Type);
- if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) ||
- !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType))
- return false;
-
- NegatePrimitiveValue(ref result, operandCode);
- return true;
- }
-
- if (expr is ConstantExpression constExpr)
- return TryUnboxToPrimitiveValue(ref result, constExpr.Value, exprCode);
-
- if (nodeType == ExpressionType.Default)
- return TrySetPrimitiveValueToDefault(ref result, exprCode);
-
- if (nodeType == ExpressionType.Convert)
- {
- var operandExpr = ((UnaryExpression)expr).Operand;
- var operandCode = Type.GetTypeCode(operandExpr.Type);
- Debug.Assert(operandCode != TypeCode.Boolean,
- "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue");
-
- if (operandCode != TypeCode.Decimal)
- {
- if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) ||
- !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType))
- return false;
-
- if (exprCode != operandCode)
- ConvertPrimitiveValueFromTo(ref result, operandCode, exprCode);
- return true;
- }
- // then for the decimal
- {
- decimal operandDec = default;
- if (operandExpr is ConstantExpression co)
- operandDec = (decimal)co.Value;
- else if (!TryInterpretDecimal(ref operandDec, operandExpr, operandExpr.NodeType))
- return false;
-
- switch (exprCode)
- {
- case TypeCode.Char: result.CharValue = (char)operandDec; break;
- case TypeCode.SByte: result.SByteValue = (sbyte)operandDec; break;
- case TypeCode.Byte: result.ByteValue = (byte)operandDec; break;
- case TypeCode.Int16: result.Int16Value = (short)operandDec; break;
- case TypeCode.UInt16: result.UInt16Value = (ushort)operandDec; break;
- case TypeCode.Int32: result.Int32Value = (int)operandDec; break;
- case TypeCode.UInt32: result.UInt32Value = (uint)operandDec; break;
- case TypeCode.Int64: result.Int64Value = (long)operandDec; break;
- case TypeCode.UInt64: result.UInt64Value = (ulong)operandDec; break;
- case TypeCode.Single: result.SingleValue = (float)operandDec; break;
- case TypeCode.Double: result.DoubleValue = (double)operandDec; break;
- default:
- // todo: @feature #472 support conversion to nullable, put nullable marker into PValue or something
- return false;
- }
- return true;
- }
- }
- return false;
- }
- }
- }
-
- ///
- /// Helpers targeting the performance. Extensions method names may be a bit funny (non standard),
- /// in order to prevent conflicts with YOUR helpers with standard names
- ///
- public static class Tools
- {
- public static Expression AsExpr(this object obj) => obj as Expression ?? Constant(obj);
- public static Expression[] AsExprs(this object[] obj)
- {
- var exprs = new Expression[obj.Length];
- for (var i = 0; i < obj.Length; i++)
- exprs[i] = obj[i].AsExpr();
- return exprs;
- }
-
- /// Returns true if class is compiler generated. Checking for CompilerGeneratedAttribute
- /// is not enough, because this attribute is not applied for classes generated from "async/await".
- [MethodImpl((MethodImplOptions)256)]
- public static bool IsCompilerGenerated(this Type type) =>
- type.Name[0] == '<'; // consider the types with obstruct names like `<>blah` as compiler-generated
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool IsUnsigned(this Type type) =>
- type == typeof(byte) ||
- type == typeof(ushort) ||
- type == typeof(uint) ||
- type == typeof(ulong);
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool IsFloatingPoint(this Type type) =>
- type == typeof(float) ||
- type == typeof(double);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool IsInteger(this Type type) => Type.GetTypeCode(type) switch
- {
- TypeCode.Char => true,
- TypeCode.SByte => true,
- TypeCode.Byte => true,
- TypeCode.Int16 => true,
- TypeCode.UInt16 => true,
- TypeCode.Int32 => true,
- TypeCode.UInt32 => true,
- TypeCode.Int64 => true,
- TypeCode.UInt64 => true,
- _ => false
- };
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool IsIntegerOrUnderlyingInteger(this Type type) =>
- type.IsPrimitive && type.IsInteger() ||
- type.IsEnum && Enum.GetUnderlyingType(type).IsInteger();
-
- internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type)
- {
- switch (Type.GetTypeCode(type))
- {
- case TypeCode.Boolean:
- case TypeCode.Char:
- case TypeCode.SByte:
- case TypeCode.Byte:
- case TypeCode.Int16:
- case TypeCode.UInt16:
- case TypeCode.Int32:
- case TypeCode.UInt32:
- case TypeCode.Int64:
- case TypeCode.UInt64:
- case TypeCode.Single:
- case TypeCode.Double:
- return true;
- default:
- return false;
- }
- }
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- public static bool IsNullable(this Type type) =>
- (type.IsValueType & type.IsGenericType) && type.GetGenericTypeDefinition() == typeof(Nullable<>);
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- public static Type GetUnderlyingNullableTypeUnsafe(this Type type) => type.GetGenericArguments()[0];
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- public static Type GetNonNullableOrSelf(this Type type) => type.IsNullable() ? type.GetGenericArguments()[0] : type;
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- public static Type GetNullable(this Type type) => typeof(Nullable<>).MakeGenericType(type);
-
- public static string GetArithmeticBinaryOperatorMethodName(this ExpressionType nodeType) =>
- nodeType switch
- {
- ExpressionType.Add => "op_Addition",
- ExpressionType.AddChecked => "op_Addition",
- ExpressionType.Subtract => "op_Subtraction",
- ExpressionType.SubtractChecked => "op_Subtraction",
- ExpressionType.Multiply => "op_Multiply",
- ExpressionType.MultiplyChecked => "op_Multiply",
- ExpressionType.Divide => "op_Division",
- ExpressionType.Modulo => "op_Modulus",
- _ => null
- };
-
- internal static bool IsAssignNodeType(this ExpressionType nodeType) => nodeType switch
- {
- ExpressionType.Assign => true,
- ExpressionType.PowerAssign => true,
- ExpressionType.AndAssign => true,
- ExpressionType.OrAssign => true,
- ExpressionType.AddAssign => true,
- ExpressionType.ExclusiveOrAssign => true,
- ExpressionType.AddAssignChecked => true,
- ExpressionType.SubtractAssign => true,
- ExpressionType.SubtractAssignChecked => true,
- ExpressionType.MultiplyAssign => true,
- ExpressionType.MultiplyAssignChecked => true,
- ExpressionType.DivideAssign => true,
- ExpressionType.LeftShiftAssign => true,
- ExpressionType.RightShiftAssign => true,
- ExpressionType.ModuloAssign => true,
- _ => false
- };
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool IsBracedBlockLike(this ExpressionType nodeType) =>
- nodeType == ExpressionType.Try |
- nodeType == ExpressionType.Switch |
- nodeType == ExpressionType.Block |
- nodeType == ExpressionType.Loop;
-
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool IsBlockLikeOrConditional(this ExpressionType nodeType) =>
- nodeType == ExpressionType.Conditional | nodeType == ExpressionType.Coalesce ||
- IsBracedBlockLike(nodeType);
-
- // Returns true when a non-void expression used as a statement needs a `_ = ` discard prefix
- // to suppress CS0201 / produce valid, unambiguous C# output.
- [MethodImpl((MethodImplOptions)256)]
- internal static bool NeedsDiscardWhenUsedAsStatement(this Expression expr)
- {
- var nodeType = expr.NodeType;
- return expr.Type != typeof(void)
- && (nodeType == ExpressionType.Call | nodeType == ExpressionType.Invoke
- | nodeType == ExpressionType.Conditional | nodeType == ExpressionType.Coalesce);
- }
-
- [MethodImpl((MethodImplOptions)256)]
- internal static bool IsReturnable(this Expression expr)
- {
- var nodeType = expr.NodeType;
- return expr.Type != typeof(void) &&
- nodeType != ExpressionType.Goto & nodeType != ExpressionType.Label & nodeType != ExpressionType.Throw &&
- !IsBracedBlockLike(nodeType);
- }
-
- internal static Expression StripConvertRecursively(this Expression expr) =>
- expr is UnaryExpression convert && convert.NodeType == ExpressionType.Convert
- ? StripConvertRecursively(convert.Operand)
- : expr;
-
- internal static bool IsComplexExpression(this Expression expr)
- {
- expr = expr.StripConvertRecursively();
- return expr.NodeType == ExpressionType.Invoke
- || expr.NodeType.IsBlockLikeOrConditional();
- }
-
- internal static bool IsConstantOrDefault(this Expression expr)
- {
- var nodeType = StripConvertRecursively(expr).NodeType;
- return nodeType == ExpressionType.Constant
- | nodeType == ExpressionType.Default;
- }
-
- internal static bool IsParamOrConstantOrDefault(this Expression expr)
- {
- var nodeType = StripConvertRecursively(expr).NodeType;
- return nodeType == ExpressionType.Parameter
- | nodeType == ExpressionType.Constant
- | nodeType == ExpressionType.Default;
- }
-
- internal static string GetCSharpName(this MemberInfo m)
- {
- var name = m.Name;
- if (m is FieldInfo fi && m.DeclaringType.IsValueType)
- {
- // btw, `fi.IsSpecialName` returns `false` :/
- if (name[0] == '<') // a backing field for the properties in struct, e.g. k__BackingField
- {
- var end = name.IndexOf('>');
- if (end > 1)
- name = name.Substring(1, end - 1);
- }
- }
- return name;
- }
-
- [RequiresUnreferencedCode(Trimming.Message)]
- internal static MethodInfo FindMethod(this Type type, string methodName)
- {
- var methods = type.GetMethods();
- for (var i = 0; i < methods.Length; i++)
- if (methods[i].Name == methodName)
- return methods[i];
- return type.BaseType?.FindMethod(methodName);
- }
-
- internal static MethodInfo DelegateTargetGetterMethod =
- typeof(Delegate).GetProperty(nameof(Delegate.Target)).GetMethod;
-
- [MethodImpl((MethodImplOptions)256)]
- internal static MethodInfo FindDelegateInvokeMethod(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type) =>
- type.GetMethod("Invoke");
-
- [RequiresUnreferencedCode(Trimming.Message)]
- internal static class NullableReflected where T : struct
- {
- public static readonly Type NullableType = typeof(T?);
- public static readonly MethodInfo ValueGetterMethod =
- NullableType.GetProperty("Value").GetMethod;
- public static readonly MethodInfo HasValueGetterMethod =
- NullableType.GetProperty("HasValue").GetMethod;
- public static readonly FieldInfo ValueField =
- NullableType.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
- public static readonly ConstructorInfo Constructor =
- NullableType.GetConstructors()[0];
- }
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- internal static MethodInfo GetNullableValueGetterMethod(this Type type) =>
- type == typeof(int?) ? NullableReflected.ValueGetterMethod :
- type == typeof(double?) ? NullableReflected.ValueGetterMethod :
- type.GetProperty("Value").GetMethod;
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- internal static MethodInfo GetNullableHasValueGetterMethod(this Type type) =>
- type == typeof(int?) ? NullableReflected.HasValueGetterMethod :
- type == typeof(double?) ? NullableReflected.HasValueGetterMethod :
- type.GetProperty("HasValue").GetMethod;
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- internal static FieldInfo GetNullableValueUnsafeAkaGetValueOrDefaultMethod(this Type type) =>
- type == typeof(int?) ? NullableReflected.ValueField :
- type == typeof(double?) ? NullableReflected.ValueField :
- type.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
-
- [RequiresUnreferencedCode(Trimming.Message)]
- [MethodImpl((MethodImplOptions)256)]
- internal static ConstructorInfo GetNullableConstructor(this Type type) =>
- type == typeof(int?) ? NullableReflected.Constructor :
- type == typeof(double?) ? NullableReflected.Constructor :
- type.GetConstructors()[0];
-
- /// Finds the implicit or explicit conversion operator inType from the sourceType to targetType,
- /// otherwise returns null
- [RequiresUnreferencedCode(Trimming.Message)]
- public static MethodInfo FindConvertOperator(this Type inType, Type sourceType, Type targetType)
- {
- Debug.Assert(!inType.IsNullable(), "Should not be called for the nullable type");
- Debug.Assert(!inType.IsPrimitive, "Should not be called for the primitive type");
- Debug.Assert(!inType.IsEnum, "Should not be called for the enum type");
-
- // note: remember that if inType.IsPrimitive it does contain the explicit or implicit conversion operators at all
- if (sourceType == typeof(object) | targetType == typeof(object))
- return null;
-
- // conversion operators should be declared as static and public
- var methods = inType.GetMethods(BindingFlags.Static | BindingFlags.Public);
- foreach (var m in methods)
- if (m.IsSpecialName && m.ReturnType == targetType)
- {
- var n = m.Name;
- if ((n == "op_Implicit" || n == "op_Explicit") &&
- m.GetParameters()[0].ParameterType == sourceType)
- return m;
- }
-
- return null;
- }
-
- [RequiresUnreferencedCode(Trimming.Message)]
- internal static ConstructorInfo FindSingleParamConstructor(this Type type, Type paramType)
- {
- var ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
- for (var i = 0; i < ctors.Length; i++)
- {
- var ctor = ctors[i];
- var parameters = ctor.GetParameters();
- if (parameters.Length == 1 && parameters[0].ParameterType == paramType)
- return ctor;
- }
-
- return null;
- }
-
- public static T[] AsArray(this IEnumerable xs)
- {
- if (xs is T[] array)
- return array;
- return xs == null ? null : xs.ToArray();
- }
-
- internal static IList AsList