public QueryEvaluatorExpressionHeap(IConstantHoister hoister)
     : base(hoister)
 {
 }
예제 #2
0
 /// <summary>
 /// Initializes the expression heap with a default template cache.
 /// </summary>
 /// <param name="hoister">The constant hoister.</param>
 public ExpressionHeap(IConstantHoister hoister)
     : this(hoister, comparer => new Cache <Expression>(new CacheStorage <Expression>(comparer)))
 {
 }
예제 #3
0
 /// <summary>
 /// Initializes the expression heap with the given template cache.
 /// </summary>
 /// <param name="hoister">The constant hoister.</param>
 /// <param name="cacheFactory">
 /// Creates the template cache using the given equality comparer.
 /// </param>
 public ExpressionHeap(IConstantHoister hoister, Func <IEqualityComparer <Expression>, ICache <Expression> > cacheFactory)
     : base((cacheFactory ?? throw new ArgumentNullException(nameof(cacheFactory)))(Comparer.Instance))
예제 #4
0
 public Visitor(ICompiledDelegateCache cache, IConstantHoister hoister)
 {
     _cache   = cache;
     _hoister = hoister;
 }
예제 #5
0
        /// <summary>
        /// Compiles the specified lambda expression by hoisting constants from the expression and consulting
        /// the cache for the resulting templatized lambda expression. This technique increases the likelihood
        /// for a cache hit.
        /// </summary>
        /// <param name="expression">Lambda expression to compile.</param>
        /// <param name="cache">Cache to hold the compiled delegate.</param>
        /// <param name="outliningEnabled">Specifies whether nested lambda expressions should be outlined into delegate constants by recursive compilation using the cache.</param>
        /// <param name="hoister">Constant hoister used to selectively hoist constants in the specified expression.</param>
        /// <returns>Compiled delegate for the specified lambda expression.</returns>
        public static Delegate Compile(this LambdaExpression expression, ICompiledDelegateCache cache, bool outliningEnabled, IConstantHoister hoister)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }
            if (cache == null)
            {
                throw new ArgumentNullException(nameof(cache));
            }
            if (hoister == null)
            {
                throw new ArgumentNullException(nameof(hoister));
            }

            return(CompileImpl(expression, cache, outliningEnabled, hoister));
        }
예제 #6
0
 /// <summary>
 /// Outlines nested lambda expressions in the specified expression. If the expression is a
 /// lambda expression itself, it will be considered for rewriting as well. Care should be
 /// taken when calling this method as not to trigger a stack overflow.
 /// </summary>
 /// <param name="expression">Expression to apply nested lambda expression outlining on.</param>
 /// <param name="cache">Cache to hold any recursively compiled delegates.</param>
 /// <param name="hoister">Constant hoister used to selectively hoist constants in the specified expression.</param>
 /// <returns>Original expression with outlining steps applied.</returns>
 public static Expression Outline(Expression expression, ICompiledDelegateCache cache, IConstantHoister hoister) => new Visitor(cache, hoister).Visit(expression);
예제 #7
0
            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);
            }
예제 #8
0
        /// <summary>
        /// Outlines nested lambda expressions for consideration in the constant hoisting step. Only non-quoted
        /// lambda expressions without a closure will be considered for hoisting and subsequent caching.
        /// </summary>
        /// <param name="lambda">Lambda expression to perform outlining of inner lambda expressions on.</param>
        /// <param name="cache">Cache to hold any recursively compiled delegates.</param>
        /// <param name="hoister">Constant hoister used to selectively hoist constants in the specified expression.</param>
        /// <returns>Original lambda expression with outlined inner lambda expressions substituted for constant delegate expressions.</returns>
        /// <remarks>Future extensions can provide for a means to control the outlining policy, e.g. to insert a thunk to defer recursive compilation.</remarks>
        private static LambdaExpression Outline(LambdaExpression lambda, ICompiledDelegateCache cache, IConstantHoister hoister)
        {
            var oldBody = lambda.Body;

            var newBody = ExpressionOutliner.Outline(oldBody, cache, hoister);

            if (ReferenceEquals(oldBody, newBody))
            {
                return(lambda);
            }

            var result = Expression.Lambda(lambda.Type, newBody, lambda.Name, lambda.TailCall, lambda.Parameters);

            return(result);
        }
예제 #9
0
        private static Delegate CompileImpl(LambdaExpression expression, ICompiledDelegateCache cache, bool outliningEnabled, IConstantHoister hoister)
        {
            //
            // E.g. xs => xs.Bar(x => x > 0).Foo(x => x * 2)
            //
            var expr = expression;

            if (outliningEnabled)
            {
                //
                // E.g. xs => xs.Bar(delegate1).Foo(delegate2)  where delegate1 and delegate2 are constants
                //
                expr = Outline(expression, cache, hoister);
            }

            var template = ExpressionTemplatizer.Templatize(expr, hoister ?? SimpleHoister.Instance);

            //
            // E.g.  outline |                                  template expressions
            //      ---------+------------------------------------------------------------------------------------------
            //          Y    | (a, b) => xs => xs.Bar(a).Foo(b)  and recursively  c => x => x > c  and  d => x => x * d
            //          N    | (c, d) => xs => xs.Bar(x => x > c).Foo(x => x * d)
            //
            var lambda   = template.Template;
            var argument = template.Argument;

            Delegate res;

            if (lambda.Parameters.Count == 0)
            {
                //
                // No template parameters, hence no tuple packing required.
                //
                res = cache.GetOrAdd(expr);
            }
            else
            {
                //
                // E.g. without outlining:  t => xs => xs.Bar(x => x > t.Item1).Foo(x => x * t.Item2)
                //
                var cachedDelegate = cache.GetOrAdd(lambda);

                //
                // E.g. new Tuple<int, int>(0, 2)
                //
                var tupleArgument = TupleEvaluator.Instance.Visit(argument);

                res = (Delegate)cachedDelegate.DynamicInvoke(new object[] { tupleArgument });
            }

            return(res);
        }
예제 #10
0
        /// <summary>
        /// Compiles the specified lambda expression by hoisting constants from the expression and consulting
        /// the cache for the resulting templatized lambda expression. This technique increases the likelihood
        /// for a cache hit.
        /// </summary>
        /// <typeparam name="T">Delegate type of the lambda expression.</typeparam>
        /// <param name="expression">Lambda expression to compile.</param>
        /// <param name="cache">Cache to hold the compiled delegate.</param>
        /// <param name="outliningEnabled">Specifies whether nested lambda expressions should be outlined into delegate constants by recursive compilation using the cache.</param>
        /// <param name="hoister">Constant hoister used to selectively hoist constants in the specified expression.</param>
        /// <returns>Compiled delegate for the specified lambda expression.</returns>
        public static T Compile <T>(this Expression <T> expression, ICompiledDelegateCache cache, bool outliningEnabled, IConstantHoister hoister)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }
            if (cache == null)
            {
                throw new ArgumentNullException(nameof(cache));
            }
            if (hoister == null)
            {
                throw new ArgumentNullException(nameof(hoister));
            }

            return((T)(object)CompileImpl(expression, cache, outliningEnabled, hoister));
        }