1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
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.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using Microsoft.Scripting.Ast;
19 using System.Linq.Expressions;
22 using System.Collections.Generic;
23 using System.Collections.ObjectModel;
24 using System.Diagnostics;
26 using System.Dynamic.Utils;
27 using System.Threading;
28 using System.Reflection;
30 namespace System.Runtime.CompilerServices {
32 /// Class responsible for runtime binding of the dynamic operations on the dynamic call site.
34 public abstract class CallSiteBinder {
35 private static readonly LabelTarget _updateLabel = Expression.Label("CallSiteBinder.UpdateLabel");
38 /// The Level 2 cache - all rules produced for the same binder.
40 internal Dictionary<Type, object> Cache;
43 /// Initializes a new instance of the <see cref="CallSiteBinder"/> class.
45 protected CallSiteBinder() {
49 /// Gets a label that can be used to cause the binding to be updated. It
50 /// indicates that the expression's binding is no longer valid.
51 /// This is typically used when the "version" of a dynamic object has
54 public static LabelTarget UpdateLabel {
55 get { return _updateLabel; }
58 private sealed class LambdaSignature<T> where T : class {
59 internal static readonly LambdaSignature<T> Instance = new LambdaSignature<T>();
61 internal readonly ReadOnlyCollection<ParameterExpression> Parameters;
62 internal readonly LabelTarget ReturnLabel;
64 private LambdaSignature() {
65 Type target = typeof(T);
66 if (!target.IsSubclassOf(typeof(MulticastDelegate))) {
67 throw Error.TypeParameterIsNotDelegate(target);
70 MethodInfo invoke = target.GetMethod("Invoke");
71 ParameterInfo[] pis = invoke.GetParametersCached();
72 if (pis[0].ParameterType != typeof(CallSite)) {
73 throw Error.FirstArgumentMustBeCallSite();
76 var @params = new ParameterExpression[pis.Length - 1];
77 for (int i = 0; i < @params.Length; i++) {
78 @params[i] = Expression.Parameter(pis[i + 1].ParameterType, "$arg" + i);
81 Parameters = new TrueReadOnlyCollection<ParameterExpression>(@params);
82 ReturnLabel = Expression.Label(invoke.GetReturnType());
87 /// Performs the runtime binding of the dynamic operation on a set of arguments.
89 /// <param name="args">An array of arguments to the dynamic operation.</param>
90 /// <param name="parameters">The array of <see cref="ParameterExpression"/> instances that represent the parameters of the call site in the binding process.</param>
91 /// <param name="returnLabel">A LabelTarget used to return the result of the dynamic binding.</param>
93 /// An Expression that performs tests on the dynamic operation arguments, and
94 /// performs the dynamic operation if hte tests are valid. If the tests fail on
95 /// subsequent occurrences of the dynamic operation, Bind will be called again
96 /// to produce a new <see cref="Expression"/> for the new argument types.
98 public abstract Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel);
101 /// Provides low-level runtime binding support. Classes can override this and provide a direct
102 /// delegate for the implementation of rule. This can enable saving rules to disk, having
103 /// specialized rules available at runtime, or providing a different caching policy.
105 /// <typeparam name="T">The target type of the CallSite.</typeparam>
106 /// <param name="site">The CallSite the bind is being performed for.</param>
107 /// <param name="args">The arguments for the binder.</param>
108 /// <returns>A new delegate which replaces the CallSite Target.</returns>
109 public virtual T BindDelegate<T>(CallSite<T> site, object[] args) where T : class {
114 internal T BindCore<T>(CallSite<T> site, object[] args) where T : class {
116 // Try to find a precompiled delegate, and return it if found.
118 T result = BindDelegate(site, args);
119 if (result != null) {
124 // Get the Expression for the binding
126 var signature = LambdaSignature<T>.Instance;
127 Expression binding = Bind(args, signature.Parameters, signature.ReturnLabel);
130 // Check the produced rule
132 if (binding == null) {
133 throw Error.NoOrInvalidRuleProduced();
137 // finally produce the new rule if we need to
139 #if !CLR2 && !SILVERLIGHT && !ANDROID && !WP75
140 // We cannot compile rules in the heterogeneous app domains since they
141 // may come from less trusted sources
142 // Silverlight always uses a homogenous appdomain, so we don’t need this check
143 if (!AppDomain.CurrentDomain.IsHomogenous) {
144 throw Error.HomogenousAppDomainRequired();
147 Expression<T> e = Stitch(binding, signature);
148 T newRule = e.Compile();
150 CacheTarget(newRule);
156 /// Adds a target to the cache of known targets. The cached targets will
157 /// be scanned before calling BindDelegate to produce the new rule.
159 /// <typeparam name="T">The type of target being added.</typeparam>
160 /// <param name="target">The target delegate to be added to the cache.</param>
161 protected void CacheTarget<T>(T target) where T : class {
162 GetRuleCache<T>().AddRule(target);
165 private static Expression<T> Stitch<T>(Expression binding, LambdaSignature<T> signature) where T : class {
166 Type siteType = typeof(CallSite<T>);
168 var body = new ReadOnlyCollectionBuilder<Expression>(3);
171 var site = Expression.Parameter(typeof(CallSite), "$site");
172 var @params = signature.Parameters.AddFirst(site);
174 Expression updLabel = Expression.Label(CallSiteBinder.UpdateLabel);
177 // put the AST into the constant pool for debugging purposes
178 updLabel = Expression.Block(
179 Expression.Constant(binding, typeof(Expression)),
187 signature.ReturnLabel,
188 Expression.Condition(
190 typeof(CallSiteOps).GetMethod("SetNotMatched"),
193 Expression.Default(signature.ReturnLabel.Type),
196 Expression.Convert(site, siteType),
197 typeof(CallSite<T>).GetProperty("Update")
199 new TrueReadOnlyCollection<Expression>(@params)
205 return new Expression<T>(
206 Expression.Block(body),
208 true, // always compile the rules with tail call optimization
209 new TrueReadOnlyCollection<ParameterExpression>(@params)
213 internal RuleCache<T> GetRuleCache<T>() where T : class {
214 // make sure we have cache.
216 Interlocked.CompareExchange(ref Cache, new Dictionary<Type, object>(), null);
222 if (!cache.TryGetValue(typeof(T), out ruleCache)) {
223 cache[typeof(T)] = ruleCache = new RuleCache<T>();
227 RuleCache<T> result = ruleCache as RuleCache<T>;
228 Debug.Assert(result != null);