1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Dynamic.Utils;
8 using System.Reflection;
9 using System.Reflection.Emit;
10 using System.Runtime.CompilerServices;
11 
12 namespace System.Linq.Expressions.Compiler
13 {
14     internal interface ILocalCache
15     {
GetLocal(Type type)16         LocalBuilder GetLocal(Type type);
17 
FreeLocal(LocalBuilder local)18         void FreeLocal(LocalBuilder local);
19     }
20 
21     /// <summary>
22     /// LambdaCompiler is responsible for compiling individual lambda (LambdaExpression). The complete tree may
23     /// contain multiple lambdas, the Compiler class is responsible for compiling the whole tree, individual
24     /// lambdas are then compiled by the LambdaCompiler.
25     /// </summary>
26     [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
27     internal sealed partial class LambdaCompiler : ILocalCache
28     {
WriteBack(LambdaCompiler compiler)29         private delegate void WriteBack(LambdaCompiler compiler);
30 
31         // Information on the entire lambda tree currently being compiled
32         private readonly AnalyzedTree _tree;
33 
34         private readonly ILGenerator _ilg;
35 
36 #if FEATURE_COMPILE_TO_METHODBUILDER
37         // The TypeBuilder backing this method, if any
38         private readonly TypeBuilder _typeBuilder;
39 #endif
40 
41         private readonly MethodInfo _method;
42 
43         // Currently active LabelTargets and their mapping to IL labels
44         private LabelScopeInfo _labelBlock = new LabelScopeInfo(null, LabelScopeKind.Lambda);
45         // Mapping of labels used for "long" jumps (jumping out and into blocks)
46         private readonly Dictionary<LabelTarget, LabelInfo> _labelInfo = new Dictionary<LabelTarget, LabelInfo>();
47 
48         // The currently active variable scope
49         private CompilerScope _scope;
50 
51         // The lambda we are compiling
52         private readonly LambdaExpression _lambda;
53 
54         // True if the method's first argument is of type Closure
55         private readonly bool _hasClosureArgument;
56 
57         // Runtime constants bound to the delegate
58         private readonly BoundConstants _boundConstants;
59 
60         // Free list of locals, so we reuse them rather than creating new ones
61         private readonly KeyedStack<Type, LocalBuilder> _freeLocals = new KeyedStack<Type, LocalBuilder>();
62 
63         /// <summary>
64         /// Creates a lambda compiler that will compile to a dynamic method
65         /// </summary>
LambdaCompiler(AnalyzedTree tree, LambdaExpression lambda)66         private LambdaCompiler(AnalyzedTree tree, LambdaExpression lambda)
67         {
68             Type[] parameterTypes = GetParameterTypes(lambda, typeof(Closure));
69 
70             var method = new DynamicMethod(lambda.Name ?? "lambda_method", lambda.ReturnType, parameterTypes, true);
71 
72             _tree = tree;
73             _lambda = lambda;
74             _method = method;
75 
76             // In a Win8 immersive process user code is not allowed to access non-W8P framework APIs through
77             // reflection or RefEmit. Framework code, however, is given an exemption.
78             // This is to make sure that user code cannot access non-W8P framework APIs via ExpressionTree.
79 
80             // TODO: This API is not available, is there an alternative way to achieve the same.
81             // method.ProfileAPICheck = true;
82 
83             _ilg = method.GetILGenerator();
84 
85             _hasClosureArgument = true;
86 
87             // These are populated by AnalyzeTree/VariableBinder
88             _scope = tree.Scopes[lambda];
89             _boundConstants = tree.Constants[lambda];
90 
91             InitializeMethod();
92         }
93 
94 #if FEATURE_COMPILE_TO_METHODBUILDER
95         /// <summary>
96         /// Creates a lambda compiler that will compile into the provided MethodBuilder
97         /// </summary>
LambdaCompiler(AnalyzedTree tree, LambdaExpression lambda, MethodBuilder method)98         private LambdaCompiler(AnalyzedTree tree, LambdaExpression lambda, MethodBuilder method)
99         {
100             var scope = tree.Scopes[lambda];
101             var hasClosureArgument = scope.NeedsClosure;
102 
103             Type[] paramTypes = GetParameterTypes(lambda, hasClosureArgument ? typeof(Closure) : null);
104 
105             method.SetReturnType(lambda.ReturnType);
106             method.SetParameters(paramTypes);
107             var parameters = lambda.Parameters;
108             // parameters are index from 1, with closure argument we need to skip the first arg
109             int startIndex = hasClosureArgument ? 2 : 1;
110             for (int i = 0, n = parameters.Count; i < n; i++)
111             {
112                 method.DefineParameter(i + startIndex, ParameterAttributes.None, parameters[i].Name);
113             }
114 
115             _tree = tree;
116             _lambda = lambda;
117             _typeBuilder = (TypeBuilder)method.DeclaringType;
118             _method = method;
119             _hasClosureArgument = hasClosureArgument;
120 
121             _ilg = method.GetILGenerator();
122 
123             // These are populated by AnalyzeTree/VariableBinder
124             _scope = scope;
125             _boundConstants = tree.Constants[lambda];
126 
127             InitializeMethod();
128         }
129 #endif
130 
131         /// <summary>
132         /// Creates a lambda compiler for an inlined lambda
133         /// </summary>
LambdaCompiler( LambdaCompiler parent, LambdaExpression lambda, InvocationExpression invocation)134         private LambdaCompiler(
135             LambdaCompiler parent,
136             LambdaExpression lambda,
137             InvocationExpression invocation)
138         {
139             _tree = parent._tree;
140             _lambda = lambda;
141             _method = parent._method;
142             _ilg = parent._ilg;
143             _hasClosureArgument = parent._hasClosureArgument;
144 #if FEATURE_COMPILE_TO_METHODBUILDER
145             _typeBuilder = parent._typeBuilder;
146 #endif
147             // inlined scopes are associated with invocation, not with the lambda
148             _scope = _tree.Scopes[invocation];
149             _boundConstants = parent._boundConstants;
150         }
151 
InitializeMethod()152         private void InitializeMethod()
153         {
154             // See if we can find a return label, so we can emit better IL
155             AddReturnLabel(_lambda);
156             _boundConstants.EmitCacheConstants(this);
157         }
158 
159         internal ILGenerator IL => _ilg;
160 
161         internal IParameterProvider Parameters => _lambda;
162 
163 #if FEATURE_COMPILE_TO_METHODBUILDER
164         internal bool CanEmitBoundConstants => _method is DynamicMethod;
165 #endif
166 
167         #region Compiler entry points
168 
169         /// <summary>
170         /// Compiler entry point
171         /// </summary>
172         /// <param name="lambda">LambdaExpression to compile.</param>
173         /// <returns>The compiled delegate.</returns>
Compile(LambdaExpression lambda)174         internal static Delegate Compile(LambdaExpression lambda)
175         {
176             lambda.ValidateArgumentCount();
177 
178             // 1. Bind lambda
179             AnalyzedTree tree = AnalyzeLambda(ref lambda);
180 
181             // 2. Create lambda compiler
182             LambdaCompiler c = new LambdaCompiler(tree, lambda);
183 
184             // 3. Emit
185             c.EmitLambdaBody();
186 
187             // 4. Return the delegate.
188             return c.CreateDelegate();
189         }
190 
191 #if FEATURE_COMPILE_TO_METHODBUILDER
192         /// <summary>
193         /// Mutates the MethodBuilder parameter, filling in IL, parameters,
194         /// and return type.
195         ///
196         /// (probably shouldn't be modifying parameters/return type...)
197         /// </summary>
Compile(LambdaExpression lambda, MethodBuilder method)198         internal static void Compile(LambdaExpression lambda, MethodBuilder method)
199         {
200             // 1. Bind lambda
201             AnalyzedTree tree = AnalyzeLambda(ref lambda);
202 
203             // 2. Create lambda compiler
204             LambdaCompiler c = new LambdaCompiler(tree, lambda, method);
205 
206             // 3. Emit
207             c.EmitLambdaBody();
208         }
209 #endif
210 
211         #endregion
212 
AnalyzeLambda(ref LambdaExpression lambda)213         private static AnalyzedTree AnalyzeLambda(ref LambdaExpression lambda)
214         {
215             // Spill the stack for any exception handling blocks or other
216             // constructs which require entering with an empty stack
217             lambda = StackSpiller.AnalyzeLambda(lambda);
218 
219             // Bind any variable references in this lambda
220             return VariableBinder.Bind(lambda);
221         }
222 
223         public LocalBuilder GetLocal(Type type) => _freeLocals.TryPop(type) ?? _ilg.DeclareLocal(type);
224 
FreeLocal(LocalBuilder local)225         public void FreeLocal(LocalBuilder local)
226         {
227             Debug.Assert(local != null);
228             _freeLocals.Push(local.LocalType, local);
229         }
230 
231         /// <summary>
232         /// Gets the argument slot corresponding to the parameter at the given
233         /// index. Assumes that the method takes a certain number of prefix
234         /// arguments, followed by the real parameters stored in Parameters
235         /// </summary>
GetLambdaArgument(int index)236         internal int GetLambdaArgument(int index)
237         {
238             return index + (_hasClosureArgument ? 1 : 0) + (_method.IsStatic ? 0 : 1);
239         }
240 
241         /// <summary>
242         /// Returns the index-th argument. This method provides access to the actual arguments
243         /// defined on the lambda itself, and excludes the possible 0-th closure argument.
244         /// </summary>
EmitLambdaArgument(int index)245         internal void EmitLambdaArgument(int index)
246         {
247             _ilg.EmitLoadArg(GetLambdaArgument(index));
248         }
249 
EmitClosureArgument()250         internal void EmitClosureArgument()
251         {
252             Debug.Assert(_hasClosureArgument, "must have a Closure argument");
253             Debug.Assert(_method.IsStatic, "must be a static method");
254             _ilg.EmitLoadArg(0);
255         }
256 
CreateDelegate()257         private Delegate CreateDelegate()
258         {
259             Debug.Assert(_method is DynamicMethod);
260 
261             return _method.CreateDelegate(_lambda.Type, new Closure(_boundConstants.ToArray(), null));
262         }
263 
264 #if FEATURE_COMPILE_TO_METHODBUILDER
CreateStaticField(string name, Type type)265         private FieldBuilder CreateStaticField(string name, Type type)
266         {
267             // We are emitting into someone else's type. We don't want name
268             // conflicts, so choose a long name that is unlikely to conflict.
269             // Naming scheme chosen here is similar to what the C# compiler
270             // uses.
271             return _typeBuilder.DefineField("<ExpressionCompilerImplementationDetails>{" + System.Threading.Interlocked.Increment(ref s_counter) + "}" + name, type, FieldAttributes.Static | FieldAttributes.Private);
272         }
273 #endif
274 
275         /// <summary>
276         /// Creates an uninitialized field suitable for private implementation details
277         /// Works with DynamicMethods or TypeBuilders.
278         /// </summary>
CreateLazyInitializedField(string name)279         private MemberExpression CreateLazyInitializedField<T>(string name)
280         {
281 #if FEATURE_COMPILE_TO_METHODBUILDER
282             if (_method is DynamicMethod)
283 #else
284             Debug.Assert(_method is DynamicMethod);
285 #endif
286             {
287                 return Expression.Field(Expression.Constant(new StrongBox<T>(default(T))), "Value");
288             }
289 #if FEATURE_COMPILE_TO_METHODBUILDER
290             else
291             {
292                 return Expression.Field(null, CreateStaticField(name, typeof(T)));
293             }
294 #endif
295         }
296     }
297 }
298