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.Diagnostics;
6 using System.Reflection;
7 using System.Reflection.Emit;
8 using static System.Linq.Expressions.CachedReflectionInfo;
9 
10 namespace System.Linq.Expressions.Compiler
11 {
12     /// <summary>
13     /// Dynamic Language Runtime Compiler.
14     /// This part compiles lambdas.
15     /// </summary>
16     internal partial class LambdaCompiler
17     {
18 #if FEATURE_COMPILE_TO_METHODBUILDER
19         private static int s_counter;
20 #endif
21 
EmitConstantArray(T[] array)22         internal void EmitConstantArray<T>(T[] array)
23         {
24 #if FEATURE_COMPILE_TO_METHODBUILDER
25             // Emit as runtime constant if possible
26             // if not, emit into IL
27             if (_method is DynamicMethod)
28 #else
29             Debug.Assert(_method is DynamicMethod);
30 #endif
31             {
32                 EmitConstant(array, typeof(T[]));
33             }
34 #if FEATURE_COMPILE_TO_METHODBUILDER
35             else if (_typeBuilder != null)
36             {
37                 // store into field in our type builder, we will initialize
38                 // the value only once.
39                 FieldBuilder fb = CreateStaticField("ConstantArray", typeof(T[]));
40                 Label l = _ilg.DefineLabel();
41                 _ilg.Emit(OpCodes.Ldsfld, fb);
42                 _ilg.Emit(OpCodes.Ldnull);
43                 _ilg.Emit(OpCodes.Bne_Un, l);
44                 _ilg.EmitArray(array, this);
45                 _ilg.Emit(OpCodes.Stsfld, fb);
46                 _ilg.MarkLabel(l);
47                 _ilg.Emit(OpCodes.Ldsfld, fb);
48             }
49             else
50             {
51                 _ilg.EmitArray(array, this);
52             }
53 #endif
54         }
55 
EmitClosureCreation(LambdaCompiler inner)56         private void EmitClosureCreation(LambdaCompiler inner)
57         {
58             bool closure = inner._scope.NeedsClosure;
59             bool boundConstants = inner._boundConstants.Count > 0;
60 
61             if (!closure && !boundConstants)
62             {
63                 _ilg.EmitNull();
64                 return;
65             }
66 
67             // new Closure(constantPool, currentHoistedLocals)
68             if (boundConstants)
69             {
70                 _boundConstants.EmitConstant(this, inner._boundConstants.ToArray(), typeof(object[]));
71             }
72             else
73             {
74                 _ilg.EmitNull();
75             }
76             if (closure)
77             {
78                 _scope.EmitGet(_scope.NearestHoistedLocals.SelfVariable);
79             }
80             else
81             {
82                 _ilg.EmitNull();
83             }
84             _ilg.EmitNew(Closure_ObjectArray_ObjectArray);
85         }
86 
87         /// <summary>
88         /// Emits code which creates new instance of the delegateType delegate.
89         ///
90         /// Since the delegate is getting closed over the "Closure" argument, this
91         /// cannot be used with virtual/instance methods (inner must be static method)
92         /// </summary>
EmitDelegateConstruction(LambdaCompiler inner)93         private void EmitDelegateConstruction(LambdaCompiler inner)
94         {
95             Type delegateType = inner._lambda.Type;
96             DynamicMethod dynamicMethod = inner._method as DynamicMethod;
97 #if FEATURE_COMPILE_TO_METHODBUILDER
98             if (dynamicMethod != null)
99 #else
100             Debug.Assert(dynamicMethod != null);
101 #endif
102             {
103                 // Emit MethodInfo.CreateDelegate instead because DynamicMethod is not in Windows 8 Profile
104                 _boundConstants.EmitConstant(this, dynamicMethod, typeof(MethodInfo));
105                 _ilg.EmitType(delegateType);
106                 EmitClosureCreation(inner);
107                 _ilg.Emit(OpCodes.Callvirt, MethodInfo_CreateDelegate_Type_Object);
108                 _ilg.Emit(OpCodes.Castclass, delegateType);
109             }
110 #if FEATURE_COMPILE_TO_METHODBUILDER
111             else
112             {
113                 // new DelegateType(closure)
114                 EmitClosureCreation(inner);
115                 _ilg.Emit(OpCodes.Ldftn, inner._method);
116                 _ilg.Emit(OpCodes.Newobj, (ConstructorInfo)(delegateType.GetMember(".ctor")[0]));
117             }
118 #endif
119         }
120 
121         /// <summary>
122         /// Emits a delegate to the method generated for the LambdaExpression.
123         /// May end up creating a wrapper to match the requested delegate type.
124         /// </summary>
125         /// <param name="lambda">Lambda for which to generate a delegate</param>
126         ///
EmitDelegateConstruction(LambdaExpression lambda)127         private void EmitDelegateConstruction(LambdaExpression lambda)
128         {
129             // 1. Create the new compiler
130             LambdaCompiler impl;
131 #if FEATURE_COMPILE_TO_METHODBUILDER
132             if (_method is DynamicMethod)
133 #else
134             Debug.Assert(_method is DynamicMethod);
135 #endif
136             {
137                 impl = new LambdaCompiler(_tree, lambda);
138             }
139 #if FEATURE_COMPILE_TO_METHODBUILDER
140             else
141             {
142                 // When the lambda does not have a name or the name is empty, generate a unique name for it.
143                 string name = String.IsNullOrEmpty(lambda.Name) ? GetUniqueMethodName() : lambda.Name;
144                 MethodBuilder mb = _typeBuilder.DefineMethod(name, MethodAttributes.Private | MethodAttributes.Static);
145                 impl = new LambdaCompiler(_tree, lambda, mb);
146             }
147 #endif
148 
149             // 2. emit the lambda
150             // Since additional ILs are always emitted after the lambda's body, should not emit with tail call optimization.
151             impl.EmitLambdaBody(_scope, false, CompilationFlags.EmitAsNoTail);
152 
153             // 3. emit the delegate creation in the outer lambda
154             EmitDelegateConstruction(impl);
155         }
156 
GetParameterTypes(LambdaExpression lambda, Type firstType)157         private static Type[] GetParameterTypes(LambdaExpression lambda, Type firstType)
158         {
159             int count = lambda.ParameterCount;
160 
161             Type[] result;
162             int i;
163 
164             if (firstType != null)
165             {
166                 result = new Type[count + 1];
167                 result[0] = firstType;
168                 i = 1;
169             }
170             else
171             {
172                 result = new Type[count];
173                 i = 0;
174             }
175 
176             for (var j = 0; j < count; j++, i++)
177             {
178                 ParameterExpression p = lambda.GetParameter(j);
179                 result[i] = p.IsByRef ? p.Type.MakeByRefType() : p.Type;
180             }
181 
182             return result;
183         }
184 
185 #if FEATURE_COMPILE_TO_METHODBUILDER
GetUniqueMethodName()186         private static string GetUniqueMethodName()
187         {
188             return "<ExpressionCompilerImplementationDetails>{" + System.Threading.Interlocked.Increment(ref s_counter) + "}lambda_method";
189         }
190 #endif
191 
EmitLambdaBody()192         private void EmitLambdaBody()
193         {
194             // The lambda body is the "last" expression of the lambda
195             CompilationFlags tailCallFlag = _lambda.TailCall ? CompilationFlags.EmitAsTail : CompilationFlags.EmitAsNoTail;
196             EmitLambdaBody(null, false, tailCallFlag);
197         }
198 
199         /// <summary>
200         /// Emits the lambda body. If inlined, the parameters should already be
201         /// pushed onto the IL stack.
202         /// </summary>
203         /// <param name="parent">The parent scope.</param>
204         /// <param name="inlined">true if the lambda is inlined; false otherwise.</param>
205         /// <param name="flags">
206         /// The enum to specify if the lambda is compiled with the tail call optimization.
207         /// </param>
EmitLambdaBody(CompilerScope parent, bool inlined, CompilationFlags flags)208         private void EmitLambdaBody(CompilerScope parent, bool inlined, CompilationFlags flags)
209         {
210             _scope.Enter(this, parent);
211 
212             if (inlined)
213             {
214                 // The arguments were already pushed onto the IL stack.
215                 // Store them into locals, popping in reverse order.
216                 //
217                 // If any arguments were ByRef, the address is on the stack and
218                 // we'll be storing it into the variable, which has a ref type.
219                 for (int i = _lambda.ParameterCount - 1; i >= 0; i--)
220                 {
221                     _scope.EmitSet(_lambda.GetParameter(i));
222                 }
223             }
224 
225             // Need to emit the expression start for the lambda body
226             flags = UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitExpressionStart);
227             if (_lambda.ReturnType == typeof(void))
228             {
229                 EmitExpressionAsVoid(_lambda.Body, flags);
230             }
231             else
232             {
233                 EmitExpression(_lambda.Body, flags);
234             }
235 
236             // Return must be the last instruction in a CLI method.
237             // But if we're inlining the lambda, we want to leave the return
238             // value on the IL stack.
239             if (!inlined)
240             {
241                 _ilg.Emit(OpCodes.Ret);
242             }
243 
244             _scope.Exit();
245 
246             // Validate labels
247             Debug.Assert(_labelBlock.Parent == null && _labelBlock.Kind == LabelScopeKind.Lambda);
248             foreach (LabelInfo label in _labelInfo.Values)
249             {
250                 label.ValidateFinish();
251             }
252         }
253     }
254 }
255