in System.Runtime.CompilerServices:
[mono.git] / mcs / class / System.Core / System.Linq.Expressions / EmitContext.cs
1 //
2 // EmitContext.cs
3 //
4 // Author:
5 //   Miguel de Icaza (miguel@novell.com)
6 //   Jb Evain (jbevain@novell.com)
7 //
8 // (C) 2008 Novell, Inc. (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System;
31 using System.Collections.ObjectModel;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.Linq;
35 using System.Reflection;
36 using System.Reflection.Emit;
37 using System.Runtime.CompilerServices;
38
39 namespace System.Linq.Expressions {
40
41         class CompilationContext {
42
43                 class HoistedVariablesDetector : ExpressionVisitor {
44
45                         Dictionary<ParameterExpression, LambdaExpression> parameter_to_lambda =
46                                 new Dictionary<ParameterExpression, LambdaExpression> ();
47
48                         Dictionary<LambdaExpression, List<ParameterExpression>> hoisted_map;
49
50                         LambdaExpression lambda;
51
52                         public Dictionary<LambdaExpression, List<ParameterExpression>> Process (LambdaExpression lambda)
53                         {
54                                 Visit (lambda);
55                                 return hoisted_map;
56                         }
57
58                         protected override void VisitLambda (LambdaExpression lambda)
59                         {
60                                 this.lambda = lambda;
61                                 foreach (var parameter in lambda.Parameters)
62                                         parameter_to_lambda [parameter] = lambda;
63                                 base.VisitLambda (lambda);
64                         }
65
66                         protected override void VisitParameter (ParameterExpression parameter)
67                         {
68                                 if (lambda.Parameters.Contains (parameter))
69                                         return;
70
71                                 Hoist (parameter);
72                         }
73
74                         void Hoist (ParameterExpression parameter)
75                         {
76                                 LambdaExpression lambda;
77                                 if (!parameter_to_lambda.TryGetValue (parameter, out lambda))
78                                         return;
79
80                                 if (hoisted_map == null)
81                                         hoisted_map = new Dictionary<LambdaExpression, List<ParameterExpression>> ();
82
83                                 List<ParameterExpression> hoisted;
84                                 if (!hoisted_map.TryGetValue (lambda, out hoisted)) {
85                                         hoisted = new List<ParameterExpression> ();
86                                         hoisted_map [lambda] = hoisted;
87                                 }
88
89                                 hoisted.Add (parameter);
90                         }
91                 }
92
93                 List<object> globals = new List<object> ();
94                 List<EmitContext> units = new List<EmitContext> ();
95                 Dictionary<LambdaExpression, List<ParameterExpression>> hoisted_map;
96
97                 public int AddGlobal (object global)
98                 {
99                         return AddItemToList (global, globals);
100                 }
101
102                 public object [] GetGlobals ()
103                 {
104                         return globals.ToArray ();
105                 }
106
107                 static int AddItemToList<T> (T item, IList<T> list)
108                 {
109                         list.Add (item);
110                         return list.Count - 1;
111                 }
112
113                 public int AddCompilationUnit (LambdaExpression lambda)
114                 {
115                         DetectHoistedVariables (lambda);
116                         return AddCompilationUnit (null, lambda);
117                 }
118
119                 public int AddCompilationUnit (EmitContext parent, LambdaExpression lambda)
120                 {
121                         var context = new EmitContext (this, parent, lambda);
122                         var unit = AddItemToList (context, units);
123                         context.Emit ();
124                         return unit;
125                 }
126
127                 void DetectHoistedVariables (LambdaExpression lambda)
128                 {
129                         hoisted_map = new HoistedVariablesDetector ().Process (lambda);
130                 }
131
132                 public List<ParameterExpression> GetHoistedLocals (LambdaExpression lambda)
133                 {
134                         if (hoisted_map == null)
135                                 return null;
136
137                         List<ParameterExpression> hoisted;
138                         hoisted_map.TryGetValue (lambda, out hoisted);
139                         return hoisted;
140                 }
141
142                 public object [] CreateHoistedLocals (int unit)
143                 {
144                         var hoisted = GetHoistedLocals (units [unit].Lambda);
145                         return new object [hoisted == null ? 0 : hoisted.Count];
146                 }
147
148                 public Delegate CreateDelegate ()
149                 {
150                         return CreateDelegate (0, new ExecutionScope (this));
151                 }
152
153                 public Delegate CreateDelegate (int unit, ExecutionScope scope)
154                 {
155                         return units [unit].CreateDelegate (scope);
156                 }
157         }
158
159         class EmitContext {
160
161                 CompilationContext context;
162                 EmitContext parent;
163                 LambdaExpression lambda;
164                 DynamicMethod method;
165                 LocalBuilder hoisted_store;
166                 List<ParameterExpression> hoisted;
167
168                 public readonly ILGenerator ig;
169
170                 public bool HasHoistedLocals {
171                         get { return hoisted != null && hoisted.Count > 0; }
172                 }
173
174                 public LambdaExpression Lambda {
175                         get { return lambda; }
176                 }
177
178                 public EmitContext (CompilationContext context, EmitContext parent, LambdaExpression lambda)
179                 {
180                         this.context = context;
181                         this.parent = parent;
182                         this.lambda = lambda;
183                         this.hoisted = context.GetHoistedLocals (lambda);
184
185                         method = new DynamicMethod (
186                                 "lambda_method",
187                                 lambda.GetReturnType (),
188                                 CreateParameterTypes (lambda.Parameters),
189                                 typeof (ExecutionScope),
190                                 true);
191
192                         ig = method.GetILGenerator ();
193                 }
194
195                 public void Emit ()
196                 {
197                         if (HasHoistedLocals)
198                                 EmitStoreHoistedLocals ();
199
200                         lambda.EmitBody (this);
201                 }
202
203                 static Type [] CreateParameterTypes (IList<ParameterExpression> parameters)
204                 {
205                         var types = new Type [parameters.Count + 1];
206                         types [0] = typeof (ExecutionScope);
207
208                         for (int i = 0; i < parameters.Count; i++)
209                                 types [i + 1] = parameters [i].Type;
210
211                         return types;
212                 }
213
214                 public bool IsLocalParameter (ParameterExpression parameter, ref int position)
215                 {
216                         position = lambda.Parameters.IndexOf (parameter);
217                         if (position > -1) {
218                                 position++;
219                                 return true;
220                         }
221
222                         return false;
223                 }
224
225                 public Delegate CreateDelegate (ExecutionScope scope)
226                 {
227                         return method.CreateDelegate (lambda.Type, scope);
228                 }
229
230                 public void Emit (Expression expression)
231                 {
232                         expression.Emit (this);
233                 }
234
235                 public LocalBuilder EmitStored (Expression expression)
236                 {
237                         var local = ig.DeclareLocal (expression.Type);
238                         expression.Emit (this);
239                         ig.Emit (OpCodes.Stloc, local);
240
241                         return local;
242                 }
243
244                 public void EmitLoadAddress (Expression expression)
245                 {
246                         ig.Emit (OpCodes.Ldloca, EmitStored (expression));
247                 }
248
249                 public void EmitLoadSubject (Expression expression)
250                 {
251                         if (expression.Type.IsValueType) {
252                                 EmitLoadAddress (expression);
253                                 return;
254                         }
255
256                         Emit (expression);
257                 }
258
259                 public void EmitLoadSubject (LocalBuilder local)
260                 {
261                         if (local.LocalType.IsValueType) {
262                                 EmitLoadAddress (local);
263                                 return;
264                         }
265
266                         EmitLoad (local);
267                 }
268
269                 public void EmitLoadAddress (LocalBuilder local)
270                 {
271                         ig.Emit (OpCodes.Ldloca, local);
272                 }
273
274                 public void EmitLoad (LocalBuilder local)
275                 {
276                         ig.Emit (OpCodes.Ldloc, local);
277                 }
278
279                 public void EmitCall (LocalBuilder local, IList<Expression> arguments, MethodInfo method)
280                 {
281                         EmitLoadSubject (local);
282                         EmitArguments (method, arguments);
283                         EmitCall (method);
284                 }
285
286                 public void EmitCall (LocalBuilder local, MethodInfo method)
287                 {
288                         EmitLoadSubject (local);
289                         EmitCall (method);
290                 }
291
292                 public void EmitCall (Expression expression, MethodInfo method)
293                 {
294                         if (!method.IsStatic)
295                                 EmitLoadSubject (expression);
296
297                         EmitCall (method);
298                 }
299
300                 public void EmitCall (Expression expression, IList<Expression> arguments, MethodInfo method)
301                 {
302                         if (!method.IsStatic)
303                                 EmitLoadSubject (expression);
304
305                         EmitArguments (method, arguments);
306                         EmitCall (method);
307                 }
308
309                 void EmitArguments (MethodInfo method, IList<Expression> arguments)
310                 {
311                         var parameters = method.GetParameters ();
312
313                         for (int i = 0; i < parameters.Length; i++) {
314                                 var parameter = parameters [i];
315                                 var argument = arguments [i];
316
317                                 if (parameter.ParameterType.IsByRef) {
318                                         ig.Emit (OpCodes.Ldloca, EmitStored (argument));
319                                         continue;
320                                 }
321
322                                 Emit (arguments [i]);
323                         }
324                 }
325
326                 public void EmitCall (MethodInfo method)
327                 {
328                         ig.Emit (
329                                 method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call,
330                                 method);
331                 }
332
333                 public void EmitNullableHasValue (LocalBuilder local)
334                 {
335                         EmitCall (local, "get_HasValue");
336                 }
337
338                 public void EmitNullableInitialize (LocalBuilder local)
339                 {
340                         ig.Emit (OpCodes.Ldloca, local);
341                         ig.Emit (OpCodes.Initobj, local.LocalType);
342                         ig.Emit (OpCodes.Ldloc, local);
343                 }
344
345                 public void EmitNullableGetValue (LocalBuilder local)
346                 {
347                         EmitCall (local, "get_Value");
348                 }
349
350                 public void EmitNullableGetValueOrDefault (LocalBuilder local)
351                 {
352                         EmitCall (local, "GetValueOrDefault");
353                 }
354
355                 void EmitCall (LocalBuilder local, string method_name)
356                 {
357                         EmitCall (local, local.LocalType.GetMethod (method_name, Type.EmptyTypes));
358                 }
359
360                 public void EmitNullableNew (Type of)
361                 {
362                         ig.Emit (OpCodes.Newobj, of.GetConstructor (new [] { of.GetFirstGenericArgument () }));
363                 }
364
365                 public void EmitCollection<T> (IEnumerable<T> collection) where T : Expression
366                 {
367                         foreach (var expression in collection)
368                                 expression.Emit (this);
369                 }
370
371                 public void EmitCollection (IEnumerable<ElementInit> initializers, LocalBuilder local)
372                 {
373                         foreach (var initializer in initializers)
374                                 initializer.Emit (this, local);
375                 }
376
377                 public void EmitCollection (IEnumerable<MemberBinding> bindings, LocalBuilder local)
378                 {
379                         foreach (var binding in bindings)
380                                 binding.Emit (this, local);
381                 }
382
383                 public void EmitIsInst (Expression expression, Type candidate)
384                 {
385                         expression.Emit (this);
386
387                         var type = expression.Type;
388
389                         if (type.IsValueType)
390                                 ig.Emit (OpCodes.Box, type);
391
392                         ig.Emit (OpCodes.Isinst, candidate);
393                 }
394
395                 public void EmitScope ()
396                 {
397                         ig.Emit (OpCodes.Ldarg_0);
398                 }
399
400                 public void EmitReadGlobal (object global)
401                 {
402                         EmitReadGlobal (global, global.GetType ());
403                 }
404
405                 public void EmitReadGlobal (object global, Type type)
406                 {
407                         EmitScope ();
408
409                         ig.Emit (OpCodes.Ldfld, typeof (ExecutionScope).GetField ("Globals"));
410
411                         ig.Emit (OpCodes.Ldc_I4, AddGlobal (global, type));
412                         ig.Emit (OpCodes.Ldelem, typeof (object));
413
414                         EmitLoadStrongBoxValue (type);
415                 }
416
417                 public void EmitLoadStrongBoxValue (Type type)
418                 {
419                         var strongbox = type.MakeStrongBoxType ();
420
421                         ig.Emit (OpCodes.Isinst, strongbox);
422                         ig.Emit (OpCodes.Ldfld, strongbox.GetField ("Value"));
423                 }
424
425                 int AddGlobal (object value, Type type)
426                 {
427                         return context.AddGlobal (CreateStrongBox (value, type));
428                 }
429
430                 public void EmitCreateDelegate (LambdaExpression lambda)
431                 {
432                         EmitScope ();
433
434                         ig.Emit (OpCodes.Ldc_I4, AddChildContext (lambda));
435                         if (hoisted_store != null)
436                                 ig.Emit (OpCodes.Ldloc, hoisted_store);
437                         else
438                                 ig.Emit (OpCodes.Ldnull);
439
440                         ig.Emit (OpCodes.Callvirt, typeof (ExecutionScope).GetMethod ("CreateDelegate"));
441
442                         ig.Emit (OpCodes.Castclass, lambda.Type);
443                 }
444
445                 void EmitStoreHoistedLocals ()
446                 {
447                         EmitHoistedLocalsStore ();
448                         for (int i = 0; i < hoisted.Count; i++)
449                                 EmitStoreHoistedLocal (i, hoisted [i]);
450                 }
451
452                 void EmitStoreHoistedLocal (int position, ParameterExpression parameter)
453                 {
454                         ig.Emit (OpCodes.Ldloc, hoisted_store);
455                         ig.Emit (OpCodes.Ldc_I4, position);
456                         parameter.Emit (this);
457                         EmitCreateStrongBox (parameter.Type);
458                         ig.Emit (OpCodes.Stelem, typeof (object));
459                 }
460
461                 void EmitCreateStrongBox (Type type)
462                 {
463                         ig.Emit (OpCodes.Newobj, type.MakeStrongBoxType ().GetConstructor (new [] { type }));
464                 }
465
466                 void EmitHoistedLocalsStore ()
467                 {
468                         EmitScope ();
469                         hoisted_store = ig.DeclareLocal (typeof (object []));
470                         ig.Emit (OpCodes.Callvirt, typeof (ExecutionScope).GetMethod ("CreateHoistedLocals"));
471                         ig.Emit (OpCodes.Stloc, hoisted_store);
472                 }
473
474                 public void EmitLoadLocals ()
475                 {
476                         ig.Emit (OpCodes.Ldfld, typeof (ExecutionScope).GetField ("Locals"));
477                 }
478
479                 public void EmitParentScope ()
480                 {
481                         ig.Emit (OpCodes.Ldfld, typeof (ExecutionScope).GetField ("Parent"));
482                 }
483
484                 public bool IsHoistedLocal (ParameterExpression parameter, ref int level, ref int position)
485                 {
486                         if (parent == null)
487                                 return false;
488
489                         if (parent.hoisted != null) {
490                                 position = parent.hoisted.IndexOf (parameter);
491                                 if (position > -1)
492                                         return true;
493                         }
494
495                         level++;
496
497                         return parent.IsHoistedLocal (parameter, ref level, ref position);
498                 }
499
500                 int AddChildContext (LambdaExpression lambda)
501                 {
502                         return context.AddCompilationUnit (this, lambda);
503                 }
504
505                 static object CreateStrongBox (object value, Type type)
506                 {
507                         return Activator.CreateInstance (
508                                 type.MakeStrongBoxType (), value);
509                 }
510         }
511 }