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;
18 using Microsoft.Scripting.Ast.Compiler;
19 using Microsoft.Scripting.Utils;
21 using System.Linq.Expressions;
22 using System.Linq.Expressions.Compiler;
25 using System.Collections.Generic;
26 using System.Collections.ObjectModel;
27 using System.Diagnostics;
29 using System.Dynamic.Utils;
30 using System.Reflection;
32 namespace System.Runtime.CompilerServices {
35 // A CallSite provides a fast mechanism for call-site caching of dynamic dispatch
36 // behvaior. Each site will hold onto a delegate that provides a fast-path dispatch
37 // based on previous types that have been seen at the call-site. This delegate will
38 // call UpdateAndExecute if it is called with types that it hasn't seen before.
39 // Updating the binding will typically create (or lookup) a new delegate
40 // that supports fast-paths for both the new type and for any types that
41 // have been seen previously.
43 // DynamicSites will generate the fast-paths specialized for sets of runtime argument
44 // types. However, they will generate exactly the right amount of code for the types
45 // that are seen in the program so that int addition will remain as fast as it would
46 // be with custom implementation of the addition, and the user-defined types can be
47 // as fast as ints because they will all have the same optimal dynamically generated
50 // DynamicSites don't encode any particular caching policy, but use their
51 // CallSiteBinding to encode a caching policy.
56 /// A Dynamic Call Site base class. This type is used as a parameter type to the
57 /// dynamic site targets. The first parameter of the delegate (T) below must be
60 public class CallSite {
62 // Cache of CallSite constructors for a given delegate type
63 private static CacheDict<Type, Func<CallSiteBinder, CallSite>> _SiteCtors;
66 /// The Binder responsible for binding operations at this call site.
67 /// This binder is invoked by the UpdateAndExecute below if all Level 0,
68 /// Level 1 and Level 2 caches experience cache miss.
70 internal readonly CallSiteBinder _binder;
72 // only CallSite<T> derives from this
73 internal CallSite(CallSiteBinder binder) {
78 /// used by Matchmaker sites to indicate rule match.
83 /// Class responsible for binding dynamic operations on the dynamic site.
85 public CallSiteBinder Binder {
86 get { return _binder; }
90 /// Creates a CallSite with the given delegate type and binder.
92 /// <param name="delegateType">The CallSite delegate type.</param>
93 /// <param name="binder">The CallSite binder.</param>
94 /// <returns>The new CallSite.</returns>
95 public static CallSite Create(Type delegateType, CallSiteBinder binder) {
96 ContractUtils.RequiresNotNull(delegateType, "delegateType");
97 ContractUtils.RequiresNotNull(binder, "binder");
98 if (!delegateType.IsSubclassOf(typeof(MulticastDelegate))) throw Error.TypeMustBeDerivedFromSystemDelegate();
100 if (_SiteCtors == null) {
101 // It's okay to just set this, worst case we're just throwing away some data
102 _SiteCtors = new CacheDict<Type, Func<CallSiteBinder, CallSite>>(100);
104 Func<CallSiteBinder, CallSite> ctor;
106 MethodInfo method = null;
107 var ctors = _SiteCtors;
109 if (!ctors.TryGetValue(delegateType, out ctor)) {
110 method = typeof(CallSite<>).MakeGenericType(delegateType).GetMethod("Create");
112 if (TypeUtils.CanCache(delegateType)) {
113 ctor = (Func<CallSiteBinder, CallSite>)Delegate.CreateDelegate(typeof(Func<CallSiteBinder, CallSite>), method);
114 ctors.Add(delegateType, ctor);
123 return (CallSite)method.Invoke(null, new object[] { binder });
128 /// Dynamic site type.
130 /// <typeparam name="T">The delegate type.</typeparam>
131 public partial class CallSite<T> : CallSite where T : class {
133 /// The update delegate. Called when the dynamic site experiences cache miss.
135 /// <returns>The update delegate.</returns>
138 // if this site is set up for match making, then use NoMatch as an Update
140 Debug.Assert(_CachedNoMatch != null, "all normal sites should have Update cached once there is an instance.");
141 return _CachedNoMatch;
143 Debug.Assert(_CachedUpdate != null, "all normal sites should have Update cached once there is an instance.");
144 return _CachedUpdate;
150 /// The Level 0 cache - a delegate specialized based on the site history.
152 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
157 /// The Level 1 cache - a history of the dynamic site.
162 // Cached update delegate for all sites with a given T
163 private static T _CachedUpdate;
165 // Cached noMatch delegate for all sites with a given T
166 private static T _CachedNoMatch;
168 private CallSite(CallSiteBinder binder)
170 Target = GetUpdateDelegate();
177 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
178 internal CallSite<T> CreateMatchMaker() {
179 return new CallSite<T>();
183 /// Creates an instance of the dynamic call site, initialized with the binder responsible for the
184 /// runtime binding of the dynamic operations at this call site.
186 /// <param name="binder">The binder responsible for the runtime binding of the dynamic operations at this call site.</param>
187 /// <returns>The new instance of dynamic call site.</returns>
188 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
189 public static CallSite<T> Create(CallSiteBinder binder) {
190 if (!typeof(T).IsSubclassOf(typeof(MulticastDelegate))) throw Error.TypeMustBeDerivedFromSystemDelegate();
191 return new CallSite<T>(binder);
194 private T GetUpdateDelegate() {
195 // This is intentionally non-static to speed up creation - in particular MakeUpdateDelegate
196 // as static generic methods are more expensive than instance methods. We call a ref helper
197 // so we only access the generic static field once.
198 return GetUpdateDelegate(ref _CachedUpdate);
201 private T GetUpdateDelegate(ref T addr) {
203 // reduce creation cost by not using Interlocked.CompareExchange. Calling I.CE causes
204 // us to spend 25% of our creation time in JIT_GenericHandle. Instead we'll rarely
205 // create 2 delegates with no other harm caused.
206 addr = MakeUpdateDelegate();
212 /// Clears the rule cache ... used by the call site tests.
214 private void ClearRuleCache() {
215 // make sure it initialized/atomized etc...
216 Binder.GetRuleCache<T>();
218 var cache = Binder.Cache;
227 const int MaxRules = 10;
228 internal void AddRule(T newRule) {
231 Rules = new[] { newRule };
236 if (rules.Length < (MaxRules - 1)) {
237 temp = new T[rules.Length + 1];
238 Array.Copy(rules, 0, temp, 1, rules.Length);
240 temp = new T[MaxRules];
241 Array.Copy(rules, 0, temp, 1, MaxRules - 1);
248 internal void MoveRule(int i) {
252 rules[i] = rules[i - 1];
253 rules[i - 1] = rules[i - 2];
257 internal T MakeUpdateDelegate() {
258 Type target = typeof(T);
260 MethodInfo invoke = target.GetMethod("Invoke");
263 if (target.IsGenericType && IsSimpleSignature(invoke, out args)) {
264 MethodInfo method = null;
265 MethodInfo noMatchMethod = null;
267 if (invoke.ReturnType == typeof(void)) {
268 if (target == DelegateHelpers.GetActionType(args.AddFirst(typeof(CallSite)))) {
269 method = typeof(UpdateDelegates).GetMethod("UpdateAndExecuteVoid" + args.Length, BindingFlags.NonPublic | BindingFlags.Static);
270 noMatchMethod = typeof(UpdateDelegates).GetMethod("NoMatchVoid" + args.Length, BindingFlags.NonPublic | BindingFlags.Static);
273 if (target == DelegateHelpers.GetFuncType(args.AddFirst(typeof(CallSite)))) {
274 method = typeof(UpdateDelegates).GetMethod("UpdateAndExecute" + (args.Length - 1), BindingFlags.NonPublic | BindingFlags.Static);
275 noMatchMethod = typeof(UpdateDelegates).GetMethod("NoMatch" + (args.Length - 1), BindingFlags.NonPublic | BindingFlags.Static);
278 if (method != null) {
279 _CachedNoMatch = (T)(object)CreateDelegateHelper(target, noMatchMethod.MakeGenericMethod(args));
280 return (T)(object)CreateDelegateHelper(target, method.MakeGenericMethod(args));
284 _CachedNoMatch = CreateCustomNoMatchDelegate(invoke);
285 return CreateCustomUpdateDelegate(invoke);
288 // NEEDS SECURITY REVIEW:
290 // This needs to be SafeCritical on Silverlight to allow access to
291 // internal types from user code as generic parameters.
293 // It's safe for a few reasons:
294 // 1. The internal types are coming from a lower trust level (app code)
295 // 2. We got the internal types from our own generic parameter: T
296 // 3. The UpdateAndExecute methods don't do anything with the types,
297 // we just want the CallSite args to be strongly typed to avoid
299 // 4. Works on desktop CLR with AppDomain that has only Execute
300 // permission. In theory it might require RestrictedMemberAccess,
301 // but it's unclear because we have tests passing without RMA.
303 // When Silverlight gets RMA we may be able to remove this.
305 [System.Security.SecuritySafeCritical]
307 private static Delegate CreateDelegateHelper(Type delegateType, MethodInfo method) {
308 return Delegate.CreateDelegate(delegateType, method);
311 private static bool IsSimpleSignature(MethodInfo invoke, out Type[] sig) {
312 ParameterInfo[] pis = invoke.GetParametersCached();
313 ContractUtils.Requires(pis.Length > 0 && pis[0].ParameterType == typeof(CallSite), "T");
315 Type[] args = new Type[invoke.ReturnType != typeof(void) ? pis.Length : pis.Length - 1];
316 bool supported = true;
318 for (int i = 1; i < pis.Length; i++) {
319 ParameterInfo pi = pis[i];
320 if (pi.IsByRefParameter()) {
323 args[i - 1] = pi.ParameterType;
325 if (invoke.ReturnType != typeof(void)) {
326 args[args.Length - 1] = invoke.ReturnType;
333 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
334 private T CreateCustomNoMatchDelegate(MethodInfo invoke) {
335 var @params = invoke.GetParametersCached().Map(p => Expression.Parameter(p.ParameterType, p.Name));
336 return Expression.Lambda<T>(
339 typeof(CallSiteOps).GetMethod("SetNotMatched"),
342 Expression.Default(invoke.GetReturnType())
349 // WARNING: If you're changing this method, make sure you update the
350 // pregenerated versions as well, which are generated by
351 // generate_dynsites.py
352 // The two implementations *must* be kept functionally equivalent!
354 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
355 private T CreateCustomUpdateDelegate(MethodInfo invoke) {
356 var body = new List<Expression>();
357 var vars = new List<ParameterExpression>();
358 var @params = invoke.GetParametersCached().Map(p => Expression.Parameter(p.ParameterType, p.Name));
359 var @return = Expression.Label(invoke.GetReturnType());
360 var typeArgs = new[] { typeof(T) };
362 var site = @params[0];
363 var arguments = @params.RemoveFirst();
365 //var @this = (CallSite<T>)site;
366 var @this = Expression.Variable(typeof(CallSite<T>), "this");
368 body.Add(Expression.Assign(@this, Expression.Convert(site, @this.Type)));
371 var applicable = Expression.Variable(typeof(T[]), "applicable");
372 vars.Add(applicable);
374 //T rule, originalRule = @this.Target;
375 var rule = Expression.Variable(typeof(T), "rule");
378 var originalRule = Expression.Variable(typeof(T), "originalRule");
379 vars.Add(originalRule);
380 body.Add(Expression.Assign(originalRule, Expression.Field(@this, "Target")));
383 ParameterExpression result = null;
384 if (@return.Type != typeof(void)) {
385 vars.Add(result = Expression.Variable(@return.Type, "result"));
389 var count = Expression.Variable(typeof(int), "count");
391 var index = Expression.Variable(typeof(int), "index");
395 //// Create matchmaker site. We'll need it regardless.
397 //site = CallSiteOps.CreateMatchmaker();
411 //// Level 1 cache lookup
413 //if ((applicable = CallSiteOps.GetRules(@this)) != null) {
414 // for (index = 0, count = applicable.Length; index < count; index++) {
415 // @this.Target = rule = applicable[i];
418 // // Execute the rule
421 // // if we've already tried it skip it...
422 // if ((object)rule != (object)originalRule) {
423 // %(setResult)s rule(site, %(args)s);
424 // if (CallSiteOps.GetMatch(site)) {
425 // CallSiteOps.UpdateRules(@this, i);
429 // // Rule didn't match, try the next one
430 // CallSiteOps.ClearMatch(site);
434 Expression invokeRule;
436 Expression getMatch = Expression.Call(
437 typeof(CallSiteOps).GetMethod("GetMatch"),
441 Expression resetMatch = Expression.Call(
442 typeof(CallSiteOps).GetMethod("ClearMatch"),
446 var onMatch = Expression.Call(
454 if (@return.Type == typeof(void)) {
455 invokeRule = Expression.Block(
456 Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params)),
459 Expression.Block(onMatch, Expression.Return(@return))
463 invokeRule = Expression.Block(
464 Expression.Assign(result, Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params))),
467 Expression.Block(onMatch, Expression.Return(@return, result))
472 Expression getRule = Expression.Assign(rule, Expression.ArrayAccess(applicable, index));
474 var @break = Expression.Label();
476 var breakIfDone = Expression.IfThen(
477 Expression.Equal(index, count),
478 Expression.Break(@break)
481 var incrementIndex = Expression.PreIncrementAssign(index);
486 Expression.Assign(applicable, Expression.Call(typeof(CallSiteOps), "GetRules", typeArgs, @this)),
487 Expression.Constant(null, applicable.Type)
490 Expression.Assign(count, Expression.ArrayLength(applicable)),
491 Expression.Assign(index, Expression.Constant(0)),
498 Expression.Convert(rule, typeof(object)),
499 Expression.Convert(originalRule, typeof(object))
503 Expression.Field(@this, "Target"),
520 //// Level 2 cache lookup
524 //// Any applicable rules in level 2 cache?
527 // var cache = CallSiteOps.GetRuleCache(@this);
529 var cache = Expression.Variable(typeof(RuleCache<T>), "cache");
535 Expression.Call(typeof(CallSiteOps), "GetRuleCache", typeArgs, @this)
539 // applicable = cache.GetRules();
544 Expression.Call(typeof(CallSiteOps), "GetCachedRules", typeArgs, cache)
548 // for (int i = 0, count = applicable.Length; i < count; i++) {
549 // @this.Target = rule = applicable[i];
552 // // Execute the rule
556 // result = rule(site, arg0);
561 // if (CallSiteOps.GetMatch(site)) {
563 // // Rule worked. Add it to level 1 cache
566 // CallSiteOps.AddRule(@this, rule);
567 // // and then move it to the front of the L2 cache
568 // CallSiteOps.MoveRule(cache, rule, index);
572 // // Rule didn't match, try the next one
573 // CallSiteOps.ClearMatch(site);
578 // L2 invokeRule is different (no onMatch)
579 if (@return.Type == typeof(void)) {
580 invokeRule = Expression.Block(
581 Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params)),
584 Expression.Return(@return)
588 invokeRule = Expression.Block(
589 Expression.Assign(result, Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params))),
592 Expression.Return(@return, result)
597 var tryRule = Expression.TryFinally(
602 Expression.Call(typeof(CallSiteOps), "AddRule", typeArgs, @this, rule),
603 Expression.Call(typeof(CallSiteOps), "MoveRule", typeArgs, cache, rule, index)
608 getRule = Expression.Assign(
609 Expression.Field(@this, "Target"),
610 Expression.Assign(rule, Expression.ArrayAccess(applicable, index))
613 body.Add(Expression.Assign(index, Expression.Constant(0)));
614 body.Add(Expression.Assign(count, Expression.ArrayLength(applicable)));
630 //// Miss on Level 0, 1 and 2 caches. Create new rule
634 body.Add(Expression.Assign(rule, Expression.Constant(null, rule.Type)));
636 //var args = new object[] { arg0, arg1, ... };
637 var args = Expression.Variable(typeof(object[]), "args");
642 Expression.NewArrayInit(typeof(object), arguments.Map(p => Convert(p, typeof(object))))
647 // @this.Target = originalRule;
648 // rule = @this.Target = @this.Binder.BindDelegate(@this, args);
651 // // Execute the rule on the matchmaker site
655 // %(setResult)s ruleTarget(site, %(args)s);
662 // // The rule worked. Add it to level 1 cache.
664 // CallSiteOps.AddRule(@this, rule);
668 // // Rule we got back didn't work, try another one
672 Expression setOldTarget = Expression.Assign(
673 Expression.Field(@this, "Target"),
677 getRule = Expression.Assign(
678 Expression.Field(@this, "Target"),
685 Expression.Property(@this, "Binder"),
692 tryRule = Expression.TryFinally(
696 Expression.Call(typeof(CallSiteOps), "AddRule", typeArgs, @this, rule)
702 Expression.Block(setOldTarget, getRule, tryRule, resetMatch),
707 body.Add(Expression.Default(@return.Type));
709 var lambda = Expression.Lambda<T>(
713 new ReadOnlyCollection<ParameterExpression>(vars),
714 new ReadOnlyCollection<Expression>(body)
718 true, // always compile the rules with tail call optimization
719 new ReadOnlyCollection<ParameterExpression>(@params)
722 // Need to compile with forceDynamic because T could be invisible,
723 // or one of the argument types could be invisible
724 return lambda.Compile();
727 private static Expression Convert(Expression arg, Type type) {
728 if (TypeUtils.AreReferenceAssignable(type, arg.Type)) {
731 return Expression.Convert(arg, type);