public static ExpressionTemplate Templatize(Expression e, IConstantHoister hoister)
            {
                var env = hoister.Hoist(e);

                var bindings = env.Bindings;
                var n        = bindings.Count;

                var res = new ExpressionTemplate();

                if (n == 0)
                {
                    res.Template = Expression.Lambda(e);
                }
                else
                {
                    var parameters = new ParameterExpression[n];
                    var arguments  = new Expression[n];

                    for (var i = 0; i < n; i++)
                    {
                        var c = bindings[i];
                        parameters[i] = c.Parameter;
                        arguments[i]  = c.Value;
                    }

                    //
                    // In case you wonder why we're not building a LambdaExpression from the parameters
                    // and the visited body, there are two reasons.
                    //
                    //
                    // The first reason is due to the way the LambdaCompiler generates code for closures,
                    // which can be illustrated with this example:
                    //
                    //   (c1, c2, c3) => (arg1, arg2) => f(arg1, c1, arg2, c2, c3)
                    //
                    // In here, the outermost lambda contains the hoisted constants, and the innermost
                    // lambda matches the original lambda's signature. When we compile this higher-order
                    // expression and then invoke the outer delegate to re-supply the constants, we end
                    // up with a delegate whose target is a System.Runtime.CompilerServices.Closure which
                    // contains two fields:
                    //
                    //   object[] Constants;
                    //   object[] Locals;
                    //
                    // Due to constant hoisting, the first array is empty. The second array will hold
                    // the variables that are closed over, in the form of StrongBox<T> objects, so we
                    // end up with the Locals containing:
                    //
                    //   new object[]
                    //   {
                    //       new StrongBox<T1> { Value = c1 },
                    //       new StrongBox<T2> { Value = c2 },
                    //       new StrongBox<T3> { Value = c3 },
                    //   }
                    //
                    // Uses of c1, c2, and c3 inside the inner lambda will effectively become accesses
                    // to the closure using a field traversal like this:
                    //
                    //   ((StrongBox<T1>)closure.Locals[0]).Value
                    //
                    // For N constants we have N allocations of a StrongBox<T>. If instead we use a
                    // single tuple to hold all of the constants, we reduce this cost slightly, at the
                    // expense of requiring one more property lookup to access the constant at runtime.
                    //
                    // NB: We could consider using a ValueTuple in the future (which was added to .NET
                    //     much later than the original implementation of this library) to avoid the
                    //     cost of accessing properties, though we should have a hard look at code gen
                    //     to a) make sure that the JITted code does not already elide the call, and
                    //     more importantly b) that no copies of ValueTuple values are made, and c) that
                    //     we don't end up just boxing the ValueTuple and thus undo the potential gains.
                    //
                    //
                    // The second (and original) reason is quite subtle. For lambda expressions with an
                    // arity of 17 and beyond the Expression.Lambda factory method will use lightweight
                    // code generation to create a delegate type. The code for this can be found in:
                    //
                    //  %DDROOT%\sources\ndp\fx\src\Core\Microsoft\Scripting\Compiler\AssemblyGen.cs
                    //
                    // The dynamic assembly used to host those delegate types is generated with the Run
                    // option rather than RunAndCollect. If we end up creating a lambda expression that
                    // uses LCG-generated types that are marked as RunAndCollect, an exception occurs:
                    //
                    //  System.NotSupportedException: A non-collectible assembly may not reference a collectible assembly.
                    //    at System.Reflection.Emit.ModuleBuilder.GetTypeRef(RuntimeModule module, String strFullName, RuntimeModule refedModule, String strRefedModuleFileName, Int32 tkResolution)
                    //    at System.Reflection.Emit.ModuleBuilder.GetTypeRefNested(Type type, Module refedModule, String strRefedModuleFileName)
                    //    at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
                    //    at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
                    //    at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
                    //    at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
                    //    at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelper(Type clsArgument, Type[] requiredCustomModifiers, Type[] optionalCustomModifiers)
                    //    at System.Reflection.Emit.SignatureHelper.AddArguments(Type[] arguments, Type[][] requiredCustomModifiers, Type[][] optionalCustomModifiers)
                    //    at System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(Module scope, CallingConventions callingConvention, Int32 cGenericParam, Type returnType, Type[] requiredReturnTypeCustomModifiers, Type[] optionalReturnTypeCustomModifiers, Type[] parameterTypes, Type[][] requiredParameterTypeCustomModifiers, Type[][] optionalParameterTypeCustomModifiers)
                    //    at System.Reflection.Emit.MethodBuilder.GetMethodSignature()
                    //    at System.Reflection.Emit.MethodBuilder.GetTokenNoLock()
                    //    at System.Reflection.Emit.MethodBuilder.GetToken()
                    //    at System.Reflection.Emit.MethodBuilder.SetImplementationFlags(MethodImplAttributes attributes)
                    //    at System.Linq.Expressions.Compiler.DelegateHelpers.MakeNewCustomDelegate(Type[] types)
                    //    at System.Linq.Expressions.Compiler.DelegateHelpers.MakeDelegateType(Type[] types)
                    //    at System.Linq.Expressions.Expression.Lambda(Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
                    //    at System.Linq.Expressions.Expression.Lambda(Expression body, ParameterExpression[] parameters)
                    //
                    // To work around this limitation, we sidestep the creation of a lambda altogether
                    // and use a specialized overload to Pack that builds a tupletized lambda from the
                    // specified body and parameters collection.
                    //
                    res.Template = ExpressionTupletizer.Pack(env.Expression, parameters);
                    res.Argument = ExpressionTupletizer.Pack(arguments, setNewExpressionMembers: false);
                }

                return(res);
            }
Example #2
0
        /// <summary>
        /// Templatizes an expression.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <returns>The expression template.</returns>
        public static ExpressionTemplate Templatize(this Expression expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

            ICollection <ParameterExpression> globals = ExpressionHelpers.FindFreeVariables(expression);

            IExpressionWithEnvironment hoisted = s_constantHoister.Hoist(expression);
            IReadOnlyList <System.Linq.CompilerServices.Binding> hoistedBindings = hoisted.Bindings;

            var hoistedBindingsCount = hoistedBindings.Count;

            var n = globals.Count + hoistedBindingsCount;

            var res = new ExpressionTemplate();

            if (n == 0)
            {
                res.Template = Expression.Lambda(expression);
            }
            else
            {
                var parameters = new ParameterExpression[n];
                var arguments  = new Expression[n];

                int i = 0;

                while (i < hoistedBindingsCount)
                {
                    var c = hoistedBindings[i];
                    parameters[i] = c.Parameter;
                    arguments[i]  = c.Value;

                    i++;
                }

                foreach (var p in globals)
                {
                    parameters[i] = p;
                    arguments[i]  = p;

                    i++;
                }

                Debug.Assert(i == n);

                //
                // In case you wonder why we're not building a LambdaExpression from the parameters
                // and the visited body, the reason is quite subtle. For lambda expressions with an
                // arity of 17 and beyond the Expression.Lambda factory method will use lightweight
                // code generation to create a delegate type. The code for this can be found in:
                //
                //  %DDROOT%\sources\ndp\fx\src\Core\Microsoft\Scripting\Compiler\AssemblyGen.cs
                //
                // The dynamic assembly used to host those delegate types is generated with the Run
                // option rather than RunAndCollect. If we end up creating a lambda expression that
                // uses LCG-generated types that are marked as RunAndCollect, an exception occurs:
                //
                //  System.NotSupportedException: A non-collectible assembly may not reference a collectible assembly.
                //    at System.Reflection.Emit.ModuleBuilder.GetTypeRef(RuntimeModule module, String strFullName, RuntimeModule refedModule, String strRefedModuleFileName, Int32 tkResolution)
                //    at System.Reflection.Emit.ModuleBuilder.GetTypeRefNested(Type type, Module refedModule, String strRefedModuleFileName)
                //    at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
                //    at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
                //    at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
                //    at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
                //    at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelper(Type clsArgument, Type[] requiredCustomModifiers, Type[] optionalCustomModifiers)
                //    at System.Reflection.Emit.SignatureHelper.AddArguments(Type[] arguments, Type[][] requiredCustomModifiers, Type[][] optionalCustomModifiers)
                //    at System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(Module scope, CallingConventions callingConvention, Int32 cGenericParam, Type returnType, Type[] requiredReturnTypeCustomModifiers, Type[] optionalReturnTypeCustomModifiers, Type[] parameterTypes, Type[][] requiredParameterTypeCustomModifiers, Type[][] optionalParameterTypeCustomModifiers)
                //    at System.Reflection.Emit.MethodBuilder.GetMethodSignature()
                //    at System.Reflection.Emit.MethodBuilder.GetTokenNoLock()
                //    at System.Reflection.Emit.MethodBuilder.GetToken()
                //    at System.Reflection.Emit.MethodBuilder.SetImplementationFlags(MethodImplAttributes attributes)
                //    at System.Linq.Expressions.Compiler.DelegateHelpers.MakeNewCustomDelegate(Type[] types)
                //    at System.Linq.Expressions.Compiler.DelegateHelpers.MakeDelegateType(Type[] types)
                //    at System.Linq.Expressions.Expression.Lambda(Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
                //    at System.Linq.Expressions.Expression.Lambda(Expression body, ParameterExpression[] parameters)
                //
                // To work around this limitation, we sidestep the creation of a lambda altogether
                // and use a specialized overload to Pack that builds a tupletized lambda from the
                // specified body and parameters collection.
                //
                res.Template = ExpressionTupletizer.Pack(hoisted.Expression, parameters);
                res.Argument = ExpressionTupletizer.Pack(arguments);
            }

            return(res);
        }