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); }
/// <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); }