System.Drawing: added email to icon and test file headers
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Actions / CallSiteBinder.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 CLR2
17 using Microsoft.Scripting.Ast;
18 #else
19 using System.Linq.Expressions;
20 #endif
21 #if SILVERLIGHT
22 using System.Core;
23 #endif
24
25 using System.Collections.Generic;
26 using System.Collections.ObjectModel;
27 using System.Diagnostics;
28 using System.Dynamic;
29 using System.Dynamic.Utils;
30 using System.Threading;
31 using System.Reflection;
32
33 namespace System.Runtime.CompilerServices {
34     /// <summary>
35     /// Class responsible for runtime binding of the dynamic operations on the dynamic call site.
36     /// </summary>
37     public abstract class CallSiteBinder {
38         private static readonly LabelTarget _updateLabel = Expression.Label("CallSiteBinder.UpdateLabel");
39
40         /// <summary>
41         /// The Level 2 cache - all rules produced for the same binder.
42         /// </summary>
43         internal Dictionary<Type, object> Cache;
44
45         /// <summary>
46         /// Initializes a new instance of the <see cref="CallSiteBinder"/> class.
47         /// </summary>
48         protected CallSiteBinder() {
49         }
50
51         /// <summary>
52         /// Gets a label that can be used to cause the binding to be updated. It
53         /// indicates that the expression's binding is no longer valid.
54         /// This is typically used when the "version" of a dynamic object has
55         /// changed.
56         /// </summary>
57         public static LabelTarget UpdateLabel {
58             get { return _updateLabel; }
59         }
60
61         private sealed class LambdaSignature<T> where T : class {
62             internal static readonly LambdaSignature<T> Instance = new LambdaSignature<T>();
63
64             internal readonly ReadOnlyCollection<ParameterExpression> Parameters;
65             internal readonly LabelTarget ReturnLabel;
66
67             private LambdaSignature() {
68                 Type target = typeof(T);
69                 if (!target.IsSubclassOf(typeof(MulticastDelegate))) {
70                     throw Error.TypeParameterIsNotDelegate(target);
71                 }
72
73                 MethodInfo invoke = target.GetMethod("Invoke");
74                 ParameterInfo[] pis = invoke.GetParametersCached();
75                 if (pis[0].ParameterType != typeof(CallSite)) {
76                     throw Error.FirstArgumentMustBeCallSite();
77                 }
78
79                 var @params = new ParameterExpression[pis.Length - 1];
80                 for (int i = 0; i < @params.Length; i++) {
81                     @params[i] = Expression.Parameter(pis[i + 1].ParameterType, "$arg" + i);
82                 }
83
84                 Parameters = new TrueReadOnlyCollection<ParameterExpression>(@params);
85                 ReturnLabel = Expression.Label(invoke.GetReturnType());
86             }
87         }
88
89         /// <summary>
90         /// Performs the runtime binding of the dynamic operation on a set of arguments.
91         /// </summary>
92         /// <param name="args">An array of arguments to the dynamic operation.</param>
93         /// <param name="parameters">The array of <see cref="ParameterExpression"/> instances that represent the parameters of the call site in the binding process.</param>
94         /// <param name="returnLabel">A LabelTarget used to return the result of the dynamic binding.</param>
95         /// <returns>
96         /// An Expression that performs tests on the dynamic operation arguments, and
97         /// performs the dynamic operation if hte tests are valid. If the tests fail on
98         /// subsequent occurrences of the dynamic operation, Bind will be called again
99         /// to produce a new <see cref="Expression"/> for the new argument types.
100         /// </returns>
101         public abstract Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel);
102
103         /// <summary>
104         /// Provides low-level runtime binding support.  Classes can override this and provide a direct
105         /// delegate for the implementation of rule.  This can enable saving rules to disk, having
106         /// specialized rules available at runtime, or providing a different caching policy.
107         /// </summary>
108         /// <typeparam name="T">The target type of the CallSite.</typeparam>
109         /// <param name="site">The CallSite the bind is being performed for.</param>
110         /// <param name="args">The arguments for the binder.</param>
111         /// <returns>A new delegate which replaces the CallSite Target.</returns>
112         public virtual T BindDelegate<T>(CallSite<T> site, object[] args) where T : class {
113             return null;
114         }
115
116         
117         internal T BindCore<T>(CallSite<T> site, object[] args) where T : class {
118             //
119             // Try to find a precompiled delegate, and return it if found.
120             //
121             T result = BindDelegate(site, args);
122             if (result != null) {
123                 return result;
124             }
125
126             //
127             // Get the Expression for the binding
128             //
129             var signature = LambdaSignature<T>.Instance;
130             Expression binding = Bind(args, signature.Parameters, signature.ReturnLabel);
131
132             //
133             // Check the produced rule
134             //
135             if (binding == null) {
136                 throw Error.NoOrInvalidRuleProduced();
137             }
138             
139             //
140             // finally produce the new rule if we need to
141             //
142 #if !CLR2 && !SILVERLIGHT
143             // We cannot compile rules in the heterogeneous app domains since they
144             // may come from less trusted sources
145             // Silverlight always uses a homogenous appdomain, so we don’t need this check
146             if (!AppDomain.CurrentDomain.IsHomogenous) {
147                 throw Error.HomogenousAppDomainRequired();
148             }
149 #endif
150             Expression<T> e = Stitch(binding, signature);
151             T newRule = e.Compile();
152
153             CacheTarget(newRule);
154
155             return newRule;
156         }
157
158         /// <summary>
159         /// Adds a target to the cache of known targets.  The cached targets will
160         /// be scanned before calling BindDelegate to produce the new rule.
161         /// </summary>
162         /// <typeparam name="T">The type of target being added.</typeparam>
163         /// <param name="target">The target delegate to be added to the cache.</param>
164         protected void CacheTarget<T>(T target) where T : class {
165             GetRuleCache<T>().AddRule(target);
166         }
167
168         private static Expression<T> Stitch<T>(Expression binding, LambdaSignature<T> signature) where T : class {
169             Type siteType = typeof(CallSite<T>);
170
171             var body = new ReadOnlyCollectionBuilder<Expression>(3);
172             body.Add(binding);
173
174             var site = Expression.Parameter(typeof(CallSite), "$site");
175             var @params = signature.Parameters.AddFirst(site);
176
177             Expression updLabel = Expression.Label(CallSiteBinder.UpdateLabel);
178
179 #if DEBUG
180             // put the AST into the constant pool for debugging purposes
181             updLabel = Expression.Block(
182                 Expression.Constant(binding, typeof(Expression)),
183                 updLabel
184             );
185 #endif
186             
187             body.Add(updLabel);
188             body.Add(
189                 Expression.Label(
190                     signature.ReturnLabel,
191                     Expression.Condition(
192                         Expression.Call(
193                             typeof(CallSiteOps).GetMethod("SetNotMatched"),
194                             @params.First()
195                         ),
196                         Expression.Default(signature.ReturnLabel.Type),
197                         Expression.Invoke(
198                             Expression.Property(
199                                 Expression.Convert(site, siteType),
200                                 typeof(CallSite<T>).GetProperty("Update")
201                             ),
202                             new TrueReadOnlyCollection<Expression>(@params)
203                         )
204                     )
205                 )
206             );
207
208             return new Expression<T>(
209                 Expression.Block(body),
210                 "CallSite.Target",
211                 true, // always compile the rules with tail call optimization
212                 new TrueReadOnlyCollection<ParameterExpression>(@params)
213             );
214         }
215
216         internal RuleCache<T> GetRuleCache<T>() where T : class {
217             // make sure we have cache.
218             if (Cache == null) {
219                 Interlocked.CompareExchange(ref Cache, new Dictionary<Type, object>(), null);
220             }
221
222             object ruleCache;
223             var cache = Cache;
224             lock (cache) {
225                 if (!cache.TryGetValue(typeof(T), out ruleCache)) {
226                     cache[typeof(T)] = ruleCache = new RuleCache<T>();
227                 }
228             }
229
230             RuleCache<T> result = ruleCache as RuleCache<T>;
231             Debug.Assert(result != null);
232             return result;
233         }
234     }
235 }