Merge pull request #900 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Dynamic / Utils / DynamicUtils.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
5  * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
6  * copy of the license can be found in the License.html file at the root of this distribution. If 
7  * you cannot locate the  Apache License, Version 2.0, please send an email to 
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
9  * by the terms of the Apache License, Version 2.0.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15
16 #if FEATURE_TASKS
17 using System.Threading.Tasks;
18 #endif
19
20 #if FEATURE_CORE_DLR
21 using System.Linq.Expressions;
22 #else
23 using Microsoft.Scripting.Ast;
24 #endif
25
26 using System;
27 using System.Collections.ObjectModel;
28 using System.Diagnostics;
29 using System.Dynamic;
30 using System.Reflection;
31 using System.Runtime.CompilerServices;
32 using System.Threading;
33 using Microsoft.Scripting.Generation;
34 using Microsoft.Scripting.Interpreter;
35 using Microsoft.Scripting.Runtime;
36
37 #if !FEATURE_DYNAMIC_EXPRESSION_VISITOR
38 #if FEATURE_CORE_DLR
39 namespace System.Linq.Expressions {
40 #else
41 namespace Microsoft.Scripting.Ast {
42 #endif
43     public abstract class DynamicExpressionVisitor : ExpressionVisitor {
44     }
45 }
46 #endif
47
48 namespace Microsoft.Scripting.Utils {
49     using AstUtils = Microsoft.Scripting.Ast.Utils;
50
51     public static class DynamicUtils {
52         /// <summary>
53         /// Returns the list of expressions represented by the <see cref="DynamicMetaObject"/> instances.
54         /// </summary>
55         /// <param name="objects">An array of <see cref="DynamicMetaObject"/> instances to extract expressions from.</param>
56         /// <returns>The array of expressions.</returns>
57         public static Expression[] GetExpressions(DynamicMetaObject[] objects) {
58             ContractUtils.RequiresNotNull(objects, "objects");
59
60             Expression[] res = new Expression[objects.Length];
61             for (int i = 0; i < objects.Length; i++) {
62                 DynamicMetaObject mo = objects[i];
63                 res[i] = mo != null ? mo.Expression : null;
64             }
65
66             return res;
67         }
68
69         /// <summary>
70         /// Creates an instance of <see cref="DynamicMetaObject"/> for a runtime value and the expression that represents it during the binding process.
71         /// </summary>
72         /// <param name="argValue">The runtime value to be represented by the <see cref="DynamicMetaObject"/>.</param>
73         /// <param name="parameterExpression">An expression to represent this <see cref="DynamicMetaObject"/> during the binding process.</param>
74         /// <returns>The new instance of <see cref="DynamicMetaObject"/>.</returns>
75         public static DynamicMetaObject ObjectToMetaObject(object argValue, Expression parameterExpression) {
76             IDynamicMetaObjectProvider ido = argValue as IDynamicMetaObjectProvider;
77             if (ido != null) {
78                 return ido.GetMetaObject(parameterExpression);
79             } else {
80                 return new DynamicMetaObject(parameterExpression, BindingRestrictions.Empty, argValue);
81             }
82         }
83
84         /// <summary>
85         /// Produces an interpreted binding using the given binder which falls over to a compiled
86         /// binding after hitCount tries.
87         /// 
88         /// This method should be called whenever an interpreted binding is required.  Sometimes it will
89         /// return a compiled binding if a previous binding was produced and it's hit count was exhausted.
90         /// In this case the binder will not be called back for a new binding - the previous one will
91         /// be used.
92         /// </summary>
93         /// <typeparam name="T">The delegate type being used for the call site</typeparam>
94         /// <param name="binder">The binder used for the call site</param>
95         /// <param name="compilationThreshold">The number of calls before the binder should switch to a compiled mode.</param>
96         /// <param name="args">The arguments that are passed for the binding (as received in a BindDelegate call)</param>
97         /// <returns>A delegate which represents the interpreted binding.</returns>
98         public static T/*!*/ LightBind<T>(this DynamicMetaObjectBinder/*!*/ binder, object[]/*!*/ args, int compilationThreshold) where T : class {
99             ContractUtils.RequiresNotNull(binder, "binder");
100             ContractUtils.RequiresNotNull(args, "args");
101
102             return GenericInterpretedBinder<T>.Instance.Bind(binder, compilationThreshold < 0 ? LightCompiler.DefaultCompilationThreshold : compilationThreshold, args);
103         }
104
105         private class GenericInterpretedBinder<T> where T : class {
106             public static GenericInterpretedBinder<T>/*!*/ Instance = new GenericInterpretedBinder<T>();
107             private readonly ReadOnlyCollection<ParameterExpression>/*!*/ _parameters;
108             private readonly Expression/*!*/ _updateExpression;
109
110             private GenericInterpretedBinder() {
111                 var invokeMethod = typeof(T).GetMethod("Invoke");
112                 var methodParams = invokeMethod.GetParameters();
113
114                 ReadOnlyCollectionBuilder<ParameterExpression> prms = new ReadOnlyCollectionBuilder<ParameterExpression>(methodParams.Length);
115                 ReadOnlyCollectionBuilder<Expression> invokePrms = new ReadOnlyCollectionBuilder<Expression>(methodParams.Length);
116                 for (int i = 0; i < methodParams.Length; i++) {
117                     var param = Expression.Parameter(methodParams[i].ParameterType);
118                     if (i == 0) {
119                         invokePrms.Add(Expression.Convert(param, typeof(CallSite<T>)));
120                     } else {
121                         invokePrms.Add(param);
122                     }
123                     prms.Add(param);
124                 }
125
126                 _parameters = prms.ToReadOnlyCollection();
127
128                 _updateExpression = Expression.Block(
129                     Expression.Label(CallSiteBinder.UpdateLabel),
130                     Expression.Invoke(
131                         Expression.Property(
132                             invokePrms[0],
133                             typeof(CallSite<T>).GetDeclaredProperty("Update")
134                         ),
135                         invokePrms.ToReadOnlyCollection()
136                     )
137                 );
138             }
139
140             public T/*!*/ Bind(DynamicMetaObjectBinder/*!*/ binder, int compilationThreshold, object[] args) {
141                 if (CachedBindingInfo<T>.LastInterpretedFailure != null && CachedBindingInfo<T>.LastInterpretedFailure.Binder == binder) {
142                     // we failed the rule because we have a compiled target available, return the compiled target
143                     Debug.Assert(CachedBindingInfo<T>.LastInterpretedFailure.CompiledTarget != null);
144                     var res = CachedBindingInfo<T>.LastInterpretedFailure.CompiledTarget;
145                     CachedBindingInfo<T>.LastInterpretedFailure = null;
146                     return res;
147                 }
148
149                 // we haven't produced a rule yet....
150                 var bindingInfo = new CachedBindingInfo<T>(binder, compilationThreshold);
151
152                 var targetMO = DynamicMetaObject.Create(args[0], _parameters[1]); // 1 is skipping CallSite
153                 DynamicMetaObject[] argsMO = new DynamicMetaObject[args.Length - 1];
154                 for (int i = 0; i < argsMO.Length; i++) {
155                     argsMO[i] = DynamicMetaObject.Create(args[i + 1], _parameters[i + 2]);
156                 }
157                 var binding = binder.Bind(targetMO, argsMO);
158
159                 return CreateDelegate(binding, bindingInfo);
160             }
161
162             private T/*!*/ CreateDelegate(DynamicMetaObject/*!*/ binding, CachedBindingInfo<T>/*!*/ bindingInfo) {
163                 return Compile(binding, bindingInfo).LightCompile(Int32.MaxValue);
164             }
165
166             private Expression<T>/*!*/ Compile(DynamicMetaObject/*!*/ obj, CachedBindingInfo<T>/*!*/ bindingInfo) {
167                 var restrictions = obj.Restrictions.ToExpression();
168
169                 var body = Expression.Condition(
170                     new InterpretedRuleHitCheckExpression(restrictions, bindingInfo),
171                     AstUtils.Convert(obj.Expression, _updateExpression.Type),
172                     _updateExpression
173                 );
174
175                 var res = Expression.Lambda<T>(
176                     body,
177                     "CallSite.Target",
178                     true, // always compile the rules with tail call optimization
179                     _parameters
180                 );
181
182                 bindingInfo.Target = res;
183                 return res;
184             }
185
186             /// <summary>
187             /// Expression which reduces to the normal test but under the interpreter adds a count down
188             /// check which enables compiling when the count down is reached.
189             /// </summary>
190             class InterpretedRuleHitCheckExpression : Expression, IInstructionProvider {
191                 private readonly Expression/*!*/ _test;
192                 private readonly CachedBindingInfo/*!*/ _bindingInfo;
193
194                 private static readonly MethodInfo InterpretedCallSiteTest = typeof(ScriptingRuntimeHelpers).GetMethod("InterpretedCallSiteTest");
195                 public InterpretedRuleHitCheckExpression(Expression/*!*/ test, CachedBindingInfo/*!*/ bindingInfo) {
196                     Assert.NotNull(test, bindingInfo);
197
198                     _test = test;
199                     _bindingInfo = bindingInfo;
200                 }
201
202                 public override Expression Reduce() {
203                     return _test;
204                 }
205
206                 protected override Expression VisitChildren(ExpressionVisitor visitor) {
207                     var test = visitor.Visit(_test);
208                     if (test != _test) {
209                         return new InterpretedRuleHitCheckExpression(test, _bindingInfo);
210                     }
211                     return this;
212                 }
213
214                 public override bool CanReduce {
215                     get { return true; }
216                 }
217
218                 public override ExpressionType NodeType {
219                     get { return ExpressionType.Extension; }
220                 }
221
222                 public override Type Type {
223                     get { return typeof(bool); }
224                 }
225
226                 #region IInstructionProvider Members
227
228                 public void AddInstructions(LightCompiler compiler) {
229                     compiler.Compile(_test);
230                     compiler.Instructions.EmitLoad(_bindingInfo);
231                     compiler.EmitCall(InterpretedCallSiteTest);
232                 }
233
234                 #endregion
235             }
236         }
237     }
238
239
240     /// <summary>
241     /// Base class for storing information about the binding that a specific rule is applicable for.
242     /// 
243     /// We have a derived generic class but this class enables us to refer to it w/o having the
244     /// generic type information around.
245     /// 
246     /// This class tracks both the count down to when we should compile.  When we compile we
247     /// take the Expression[T] that was used before and compile it.  While this is happening
248     /// we continue to allow the interpreted code to run.  When the compilation is complete we
249     /// store a thread static which tells us what binding failed and the current rule is no
250     /// longer functional.  Finally the language binder will call us again and we'll retrieve
251     /// and return the compiled overload.
252     /// </summary>
253     abstract class CachedBindingInfo {
254         public readonly DynamicMetaObjectBinder/*!*/ Binder;
255         public int CompilationThreshold;
256
257         public CachedBindingInfo(DynamicMetaObjectBinder binder, int compilationThreshold) {
258             Binder = binder;
259             CompilationThreshold = compilationThreshold;
260         }
261
262         public abstract bool CheckCompiled();
263     }
264
265     class CachedBindingInfo<T> : CachedBindingInfo where T : class {
266         public T CompiledTarget;
267         public Expression<T> Target;
268
269         [ThreadStatic]
270         public static CachedBindingInfo<T> LastInterpretedFailure;
271
272         public CachedBindingInfo(DynamicMetaObjectBinder binder, int compilationThreshold)
273             : base(binder, compilationThreshold) {
274         }
275
276         public override bool CheckCompiled() {
277             if (Target != null) {
278                 // start compiling the target if no one else has
279                 var lambda = Interlocked.Exchange(ref Target, null);
280                 if (lambda != null) {
281 #if FEATURE_TASKS
282                     new Task(() => { CompiledTarget = lambda.Compile(); }).Start();
283 #else
284                     ThreadPool.QueueUserWorkItem(x => { CompiledTarget = lambda.Compile(); });
285 #endif
286                 }
287             }
288
289             if (CompiledTarget != null) {
290                 LastInterpretedFailure = this;
291                 return false;
292             }
293
294             return true;
295         }
296     }
297 }