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;
25 using System.Collections.Generic;
26 using System.Collections.ObjectModel;
27 using System.Diagnostics;
29 using System.Dynamic.Utils;
30 using System.Threading;
31 using System.Reflection;
33 namespace System.Runtime.CompilerServices {
35 /// Class responsible for runtime binding of the dynamic operations on the dynamic call site.
37 public abstract class CallSiteBinder {
38 private static readonly LabelTarget _updateLabel = Expression.Label("CallSiteBinder.UpdateLabel");
41 /// The Level 2 cache - all rules produced for the same binder.
43 internal Dictionary<Type, object> Cache;
46 /// Initializes a new instance of the <see cref="CallSiteBinder"/> class.
48 protected CallSiteBinder() {
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
57 public static LabelTarget UpdateLabel {
58 get { return _updateLabel; }
61 private sealed class LambdaSignature<T> where T : class {
62 internal static readonly LambdaSignature<T> Instance = new LambdaSignature<T>();
64 internal readonly ReadOnlyCollection<ParameterExpression> Parameters;
65 internal readonly LabelTarget ReturnLabel;
67 private LambdaSignature() {
68 Type target = typeof(T);
69 if (!target.IsSubclassOf(typeof(MulticastDelegate))) {
70 throw Error.TypeParameterIsNotDelegate(target);
73 MethodInfo invoke = target.GetMethod("Invoke");
74 ParameterInfo[] pis = invoke.GetParametersCached();
75 if (pis[0].ParameterType != typeof(CallSite)) {
76 throw Error.FirstArgumentMustBeCallSite();
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);
84 Parameters = new TrueReadOnlyCollection<ParameterExpression>(@params);
85 ReturnLabel = Expression.Label(invoke.GetReturnType());
90 /// Performs the runtime binding of the dynamic operation on a set of arguments.
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>
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.
101 public abstract Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel);
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.
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 {
117 internal T BindCore<T>(CallSite<T> site, object[] args) where T : class {
119 // Try to find a precompiled delegate, and return it if found.
121 T result = BindDelegate(site, args);
122 if (result != null) {
127 // Get the Expression for the binding
129 var signature = LambdaSignature<T>.Instance;
130 Expression binding = Bind(args, signature.Parameters, signature.ReturnLabel);
133 // Check the produced rule
135 if (binding == null) {
136 throw Error.NoOrInvalidRuleProduced();
140 // finally produce the new rule if we need to
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();
150 Expression<T> e = Stitch(binding, signature);
151 T newRule = e.Compile();
153 CacheTarget(newRule);
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.
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);
168 private static Expression<T> Stitch<T>(Expression binding, LambdaSignature<T> signature) where T : class {
169 Type siteType = typeof(CallSite<T>);
171 var body = new ReadOnlyCollectionBuilder<Expression>(3);
174 var site = Expression.Parameter(typeof(CallSite), "$site");
175 var @params = signature.Parameters.AddFirst(site);
177 Expression updLabel = Expression.Label(CallSiteBinder.UpdateLabel);
180 // put the AST into the constant pool for debugging purposes
181 updLabel = Expression.Block(
182 Expression.Constant(binding, typeof(Expression)),
190 signature.ReturnLabel,
191 Expression.Condition(
193 typeof(CallSiteOps).GetMethod("SetNotMatched"),
196 Expression.Default(signature.ReturnLabel.Type),
199 Expression.Convert(site, siteType),
200 typeof(CallSite<T>).GetProperty("Update")
202 new TrueReadOnlyCollection<Expression>(@params)
208 return new Expression<T>(
209 Expression.Block(body),
211 true, // always compile the rules with tail call optimization
212 new TrueReadOnlyCollection<ParameterExpression>(@params)
216 internal RuleCache<T> GetRuleCache<T>() where T : class {
217 // make sure we have cache.
219 Interlocked.CompareExchange(ref Cache, new Dictionary<Type, object>(), null);
225 if (!cache.TryGetValue(typeof(T), out ruleCache)) {
226 cache[typeof(T)] = ruleCache = new RuleCache<T>();
230 RuleCache<T> result = ruleCache as RuleCache<T>;
231 Debug.Assert(result != null);