File: CallSiteBinder.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (235 lines) | stat: -rw-r--r-- 9,563 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Apache License, Version 2.0, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

#if CLR2
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif
#if SILVERLIGHT
using System.Core;
#endif

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Dynamic;
using System.Dynamic.Utils;
using System.Threading;
using System.Reflection;

namespace System.Runtime.CompilerServices {
    /// <summary>
    /// Class responsible for runtime binding of the dynamic operations on the dynamic call site.
    /// </summary>
    public abstract class CallSiteBinder {
        private static readonly LabelTarget _updateLabel = Expression.Label("CallSiteBinder.UpdateLabel");

        /// <summary>
        /// The Level 2 cache - all rules produced for the same binder.
        /// </summary>
        internal Dictionary<Type, object> Cache;

        /// <summary>
        /// Initializes a new instance of the <see cref="CallSiteBinder"/> class.
        /// </summary>
        protected CallSiteBinder() {
        }

        /// <summary>
        /// Gets a label that can be used to cause the binding to be updated. It
        /// indicates that the expression's binding is no longer valid.
        /// This is typically used when the "version" of a dynamic object has
        /// changed.
        /// </summary>
        public static LabelTarget UpdateLabel {
            get { return _updateLabel; }
        }

        private sealed class LambdaSignature<T> where T : class {
            internal static readonly LambdaSignature<T> Instance = new LambdaSignature<T>();

            internal readonly ReadOnlyCollection<ParameterExpression> Parameters;
            internal readonly LabelTarget ReturnLabel;

            private LambdaSignature() {
                Type target = typeof(T);
                if (!target.IsSubclassOf(typeof(MulticastDelegate))) {
                    throw Error.TypeParameterIsNotDelegate(target);
                }

                MethodInfo invoke = target.GetMethod("Invoke");
                ParameterInfo[] pis = invoke.GetParametersCached();
                if (pis[0].ParameterType != typeof(CallSite)) {
                    throw Error.FirstArgumentMustBeCallSite();
                }

                var @params = new ParameterExpression[pis.Length - 1];
                for (int i = 0; i < @params.Length; i++) {
                    @params[i] = Expression.Parameter(pis[i + 1].ParameterType, "$arg" + i);
                }

                Parameters = new TrueReadOnlyCollection<ParameterExpression>(@params);
                ReturnLabel = Expression.Label(invoke.GetReturnType());
            }
        }

        /// <summary>
        /// Performs the runtime binding of the dynamic operation on a set of arguments.
        /// </summary>
        /// <param name="args">An array of arguments to the dynamic operation.</param>
        /// <param name="parameters">The array of <see cref="ParameterExpression"/> instances that represent the parameters of the call site in the binding process.</param>
        /// <param name="returnLabel">A LabelTarget used to return the result of the dynamic binding.</param>
        /// <returns>
        /// An Expression that performs tests on the dynamic operation arguments, and
        /// performs the dynamic operation if hte tests are valid. If the tests fail on
        /// subsequent occurrences of the dynamic operation, Bind will be called again
        /// to produce a new <see cref="Expression"/> for the new argument types.
        /// </returns>
        public abstract Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel);

        /// <summary>
        /// Provides low-level runtime binding support.  Classes can override this and provide a direct
        /// delegate for the implementation of rule.  This can enable saving rules to disk, having
        /// specialized rules available at runtime, or providing a different caching policy.
        /// </summary>
        /// <typeparam name="T">The target type of the CallSite.</typeparam>
        /// <param name="site">The CallSite the bind is being performed for.</param>
        /// <param name="args">The arguments for the binder.</param>
        /// <returns>A new delegate which replaces the CallSite Target.</returns>
        public virtual T BindDelegate<T>(CallSite<T> site, object[] args) where T : class {
            return null;
        }

        
        internal T BindCore<T>(CallSite<T> site, object[] args) where T : class {
            //
            // Try to find a precompiled delegate, and return it if found.
            //
            T result = BindDelegate(site, args);
            if (result != null) {
                return result;
            }

            //
            // Get the Expression for the binding
            //
            var signature = LambdaSignature<T>.Instance;
            Expression binding = Bind(args, signature.Parameters, signature.ReturnLabel);

            //
            // Check the produced rule
            //
            if (binding == null) {
                throw Error.NoOrInvalidRuleProduced();
            }
            
            //
            // finally produce the new rule if we need to
            //
#if !CLR2 && !SILVERLIGHT
            // We cannot compile rules in the heterogeneous app domains since they
            // may come from less trusted sources
            // Silverlight always uses a homogenous appdomain, so we don’t need this check
            if (!AppDomain.CurrentDomain.IsHomogenous) {
                throw Error.HomogenousAppDomainRequired();
            }
#endif
            Expression<T> e = Stitch(binding, signature);
            T newRule = e.Compile();

            CacheTarget(newRule);

            return newRule;
        }

        /// <summary>
        /// Adds a target to the cache of known targets.  The cached targets will
        /// be scanned before calling BindDelegate to produce the new rule.
        /// </summary>
        /// <typeparam name="T">The type of target being added.</typeparam>
        /// <param name="target">The target delegate to be added to the cache.</param>
        protected void CacheTarget<T>(T target) where T : class {
            GetRuleCache<T>().AddRule(target);
        }

        private static Expression<T> Stitch<T>(Expression binding, LambdaSignature<T> signature) where T : class {
            Type siteType = typeof(CallSite<T>);

            var body = new ReadOnlyCollectionBuilder<Expression>(3);
            body.Add(binding);

            var site = Expression.Parameter(typeof(CallSite), "$site");
            var @params = signature.Parameters.AddFirst(site);

            Expression updLabel = Expression.Label(CallSiteBinder.UpdateLabel);

#if DEBUG
            // put the AST into the constant pool for debugging purposes
            updLabel = Expression.Block(
                Expression.Constant(binding, typeof(Expression)),
                updLabel
            );
#endif
            
            body.Add(updLabel);
            body.Add(
                Expression.Label(
                    signature.ReturnLabel,
                    Expression.Condition(
                        Expression.Call(
                            typeof(CallSiteOps).GetMethod("SetNotMatched"),
                            @params.First()
                        ),
                        Expression.Default(signature.ReturnLabel.Type),
                        Expression.Invoke(
                            Expression.Property(
                                Expression.Convert(site, siteType),
                                typeof(CallSite<T>).GetProperty("Update")
                            ),
                            new TrueReadOnlyCollection<Expression>(@params)
                        )
                    )
                )
            );

            return new Expression<T>(
                Expression.Block(body),
                "CallSite.Target",
                true, // always compile the rules with tail call optimization
                new TrueReadOnlyCollection<ParameterExpression>(@params)
            );
        }

        internal RuleCache<T> GetRuleCache<T>() where T : class {
            // make sure we have cache.
            if (Cache == null) {
                Interlocked.CompareExchange(ref Cache, new Dictionary<Type, object>(), null);
            }

            object ruleCache;
            var cache = Cache;
            lock (cache) {
                if (!cache.TryGetValue(typeof(T), out ruleCache)) {
                    cache[typeof(T)] = ruleCache = new RuleCache<T>();
                }
            }

            RuleCache<T> result = ruleCache as RuleCache<T>;
            Debug.Assert(result != null);
            return result;
        }
    }
}