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;
29 using System.Collections.Generic;
30 using System.Collections.ObjectModel;
31 using System.Diagnostics;
33 using System.Dynamic.Utils;
34 using System.Reflection;
36 namespace System.Runtime.CompilerServices {
39 // A CallSite provides a fast mechanism for call-site caching of dynamic dispatch
40 // behvaior. Each site will hold onto a delegate that provides a fast-path dispatch
41 // based on previous types that have been seen at the call-site. This delegate will
42 // call UpdateAndExecute if it is called with types that it hasn't seen before.
43 // Updating the binding will typically create (or lookup) a new delegate
44 // that supports fast-paths for both the new type and for any types that
45 // have been seen previously.
47 // DynamicSites will generate the fast-paths specialized for sets of runtime argument
48 // types. However, they will generate exactly the right amount of code for the types
49 // that are seen in the program so that int addition will remain as fast as it would
50 // be with custom implementation of the addition, and the user-defined types can be
51 // as fast as ints because they will all have the same optimal dynamically generated
54 // DynamicSites don't encode any particular caching policy, but use their
55 // CallSiteBinding to encode a caching policy.
60 /// A Dynamic Call Site base class. This type is used as a parameter type to the
61 /// dynamic site targets. The first parameter of the delegate (T) below must be
64 public class CallSite {
66 // Cache of CallSite constructors for a given delegate type
67 private static CacheDict<Type, Func<CallSiteBinder, CallSite>> _SiteCtors;
70 /// The Binder responsible for binding operations at this call site.
71 /// This binder is invoked by the UpdateAndExecute below if all Level 0,
72 /// Level 1 and Level 2 caches experience cache miss.
74 internal readonly CallSiteBinder _binder;
76 // only CallSite<T> derives from this
77 internal CallSite(CallSiteBinder binder) {
82 /// used by Matchmaker sites to indicate rule match.
87 /// Class responsible for binding dynamic operations on the dynamic site.
89 public CallSiteBinder Binder {
90 get { return _binder; }
94 /// Creates a CallSite with the given delegate type and binder.
96 /// <param name="delegateType">The CallSite delegate type.</param>
97 /// <param name="binder">The CallSite binder.</param>
98 /// <returns>The new CallSite.</returns>
99 public static CallSite Create(Type delegateType, CallSiteBinder binder) {
100 ContractUtils.RequiresNotNull(delegateType, "delegateType");
101 ContractUtils.RequiresNotNull(binder, "binder");
102 if (!delegateType.IsSubclassOf(typeof(MulticastDelegate))) throw Error.TypeMustBeDerivedFromSystemDelegate();
104 if (_SiteCtors == null) {
105 // It's okay to just set this, worst case we're just throwing away some data
106 _SiteCtors = new CacheDict<Type, Func<CallSiteBinder, CallSite>>(100);
108 Func<CallSiteBinder, CallSite> ctor;
110 MethodInfo method = null;
111 var ctors = _SiteCtors;
113 if (!ctors.TryGetValue(delegateType, out ctor)) {
114 method = typeof(CallSite<>).MakeGenericType(delegateType).GetMethod("Create");
116 if (TypeUtils.CanCache(delegateType)) {
117 ctor = (Func<CallSiteBinder, CallSite>)Delegate.CreateDelegate(typeof(Func<CallSiteBinder, CallSite>), method);
118 ctors.Add(delegateType, ctor);
127 return (CallSite)method.Invoke(null, new object[] { binder });
132 /// Dynamic site type.
134 /// <typeparam name="T">The delegate type.</typeparam>
135 public partial class CallSite<T> : CallSite where T : class {
137 /// The update delegate. Called when the dynamic site experiences cache miss.
139 /// <returns>The update delegate.</returns>
142 // if this site is set up for match making, then use NoMatch as an Update
144 Debug.Assert(_CachedNoMatch != null, "all normal sites should have Update cached once there is an instance.");
145 return _CachedNoMatch;
147 Debug.Assert(_CachedUpdate != null, "all normal sites should have Update cached once there is an instance.");
148 return _CachedUpdate;
154 /// The Level 0 cache - a delegate specialized based on the site history.
156 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
161 /// The Level 1 cache - a history of the dynamic site.
166 // Cached update delegate for all sites with a given T
167 private static T _CachedUpdate;
169 // Cached noMatch delegate for all sites with a given T
170 private static T _CachedNoMatch;
172 private CallSite(CallSiteBinder binder)
174 Target = GetUpdateDelegate();
181 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
182 internal CallSite<T> CreateMatchMaker() {
183 return new CallSite<T>();
187 /// Creates an instance of the dynamic call site, initialized with the binder responsible for the
188 /// runtime binding of the dynamic operations at this call site.
190 /// <param name="binder">The binder responsible for the runtime binding of the dynamic operations at this call site.</param>
191 /// <returns>The new instance of dynamic call site.</returns>
192 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
193 public static CallSite<T> Create(CallSiteBinder binder) {
194 if (!typeof(T).IsSubclassOf(typeof(MulticastDelegate))) throw Error.TypeMustBeDerivedFromSystemDelegate();
195 return new CallSite<T>(binder);
198 private T GetUpdateDelegate() {
199 // This is intentionally non-static to speed up creation - in particular MakeUpdateDelegate
200 // as static generic methods are more expensive than instance methods. We call a ref helper
201 // so we only access the generic static field once.
202 return GetUpdateDelegate(ref _CachedUpdate);
205 private T GetUpdateDelegate(ref T addr) {
207 // reduce creation cost by not using Interlocked.CompareExchange. Calling I.CE causes
208 // us to spend 25% of our creation time in JIT_GenericHandle. Instead we'll rarely
209 // create 2 delegates with no other harm caused.
210 addr = MakeUpdateDelegate();
216 /// Clears the rule cache ... used by the call site tests.
218 private void ClearRuleCache() {
219 // make sure it initialized/atomized etc...
220 Binder.GetRuleCache<T>();
222 var cache = Binder.Cache;
231 const int MaxRules = 10;
232 internal void AddRule(T newRule) {
235 Rules = new[] { newRule };
240 if (rules.Length < (MaxRules - 1)) {
241 temp = new T[rules.Length + 1];
242 Array.Copy(rules, 0, temp, 1, rules.Length);
244 temp = new T[MaxRules];
245 Array.Copy(rules, 0, temp, 1, MaxRules - 1);
252 internal void MoveRule(int i) {
256 rules[i] = rules[i - 1];
257 rules[i - 1] = rules[i - 2];
261 internal T MakeUpdateDelegate() {
262 Type target = typeof(T);
264 MethodInfo invoke = target.GetMethod("Invoke");
267 if (target.IsGenericType && IsSimpleSignature(invoke, out args)) {
268 MethodInfo method = null;
269 MethodInfo noMatchMethod = null;
271 if (invoke.ReturnType == typeof(void)) {
272 if (target == DelegateHelpers.GetActionType(args.AddFirst(typeof(CallSite)))) {
273 method = typeof(UpdateDelegates).GetMethod("UpdateAndExecuteVoid" + args.Length, BindingFlags.NonPublic | BindingFlags.Static);
274 noMatchMethod = typeof(UpdateDelegates).GetMethod("NoMatchVoid" + args.Length, BindingFlags.NonPublic | BindingFlags.Static);
277 if (target == DelegateHelpers.GetFuncType(args.AddFirst(typeof(CallSite)))) {
278 method = typeof(UpdateDelegates).GetMethod("UpdateAndExecute" + (args.Length - 1), BindingFlags.NonPublic | BindingFlags.Static);
279 noMatchMethod = typeof(UpdateDelegates).GetMethod("NoMatch" + (args.Length - 1), BindingFlags.NonPublic | BindingFlags.Static);
282 if (method != null) {
283 _CachedNoMatch = (T)(object)CreateDelegateHelper(target, noMatchMethod.MakeGenericMethod(args));
284 return (T)(object)CreateDelegateHelper(target, method.MakeGenericMethod(args));
288 _CachedNoMatch = CreateCustomNoMatchDelegate(invoke);
289 return CreateCustomUpdateDelegate(invoke);
292 // NEEDS SECURITY REVIEW:
294 // This needs to be SafeCritical on Silverlight to allow access to
295 // internal types from user code as generic parameters.
297 // It's safe for a few reasons:
298 // 1. The internal types are coming from a lower trust level (app code)
299 // 2. We got the internal types from our own generic parameter: T
300 // 3. The UpdateAndExecute methods don't do anything with the types,
301 // we just want the CallSite args to be strongly typed to avoid
303 // 4. Works on desktop CLR with AppDomain that has only Execute
304 // permission. In theory it might require RestrictedMemberAccess,
305 // but it's unclear because we have tests passing without RMA.
307 // When Silverlight gets RMA we may be able to remove this.
309 [System.Security.SecuritySafeCritical]
311 private static Delegate CreateDelegateHelper(Type delegateType, MethodInfo method) {
312 return Delegate.CreateDelegate(delegateType, method);
315 private static bool IsSimpleSignature(MethodInfo invoke, out Type[] sig) {
316 ParameterInfo[] pis = invoke.GetParametersCached();
317 ContractUtils.Requires(pis.Length > 0 && pis[0].ParameterType == typeof(CallSite), "T");
319 Type[] args = new Type[invoke.ReturnType != typeof(void) ? pis.Length : pis.Length - 1];
320 bool supported = true;
322 for (int i = 1; i < pis.Length; i++) {
323 ParameterInfo pi = pis[i];
324 if (pi.IsByRefParameter()) {
327 args[i - 1] = pi.ParameterType;
329 if (invoke.ReturnType != typeof(void)) {
330 args[args.Length - 1] = invoke.ReturnType;
337 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
338 private T CreateCustomNoMatchDelegate(MethodInfo invoke) {
339 var @params = invoke.GetParametersCached().Map(p => Expression.Parameter(p.ParameterType, p.Name));
340 return Expression.Lambda<T>(
343 typeof(CallSiteOps).GetMethod("SetNotMatched"),
346 Expression.Default(invoke.GetReturnType())
353 // WARNING: If you're changing this method, make sure you update the
354 // pregenerated versions as well, which are generated by
355 // generate_dynsites.py
356 // The two implementations *must* be kept functionally equivalent!
358 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
359 private T CreateCustomUpdateDelegate(MethodInfo invoke) {
360 var body = new List<Expression>();
361 var vars = new List<ParameterExpression>();
362 var @params = invoke.GetParametersCached().Map(p => Expression.Parameter(p.ParameterType, p.Name));
363 var @return = Expression.Label(invoke.GetReturnType());
364 var typeArgs = new[] { typeof(T) };
366 var site = @params[0];
367 var arguments = @params.RemoveFirst();
369 //var @this = (CallSite<T>)site;
370 var @this = Expression.Variable(typeof(CallSite<T>), "this");
372 body.Add(Expression.Assign(@this, Expression.Convert(site, @this.Type)));
375 var applicable = Expression.Variable(typeof(T[]), "applicable");
376 vars.Add(applicable);
378 //T rule, originalRule = @this.Target;
379 var rule = Expression.Variable(typeof(T), "rule");
382 var originalRule = Expression.Variable(typeof(T), "originalRule");
383 vars.Add(originalRule);
384 body.Add(Expression.Assign(originalRule, Expression.Field(@this, "Target")));
387 ParameterExpression result = null;
388 if (@return.Type != typeof(void)) {
389 vars.Add(result = Expression.Variable(@return.Type, "result"));
393 var count = Expression.Variable(typeof(int), "count");
395 var index = Expression.Variable(typeof(int), "index");
399 //// Create matchmaker site. We'll need it regardless.
401 //site = CallSiteOps.CreateMatchmaker();
415 //// Level 1 cache lookup
417 //if ((applicable = CallSiteOps.GetRules(@this)) != null) {
418 // for (index = 0, count = applicable.Length; index < count; index++) {
419 // @this.Target = rule = applicable[i];
422 // // Execute the rule
425 // // if we've already tried it skip it...
426 // if ((object)rule != (object)originalRule) {
427 // %(setResult)s rule(site, %(args)s);
428 // if (CallSiteOps.GetMatch(site)) {
429 // CallSiteOps.UpdateRules(@this, i);
433 // // Rule didn't match, try the next one
434 // CallSiteOps.ClearMatch(site);
438 Expression invokeRule;
440 Expression getMatch = Expression.Call(
441 typeof(CallSiteOps).GetMethod("GetMatch"),
445 Expression resetMatch = Expression.Call(
446 typeof(CallSiteOps).GetMethod("ClearMatch"),
450 var onMatch = Expression.Call(
458 if (@return.Type == typeof(void)) {
459 invokeRule = Expression.Block(
460 Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params)),
463 Expression.Block(onMatch, Expression.Return(@return))
467 invokeRule = Expression.Block(
468 Expression.Assign(result, Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params))),
471 Expression.Block(onMatch, Expression.Return(@return, result))
476 Expression getRule = Expression.Assign(rule, Expression.ArrayAccess(applicable, index));
478 var @break = Expression.Label();
480 var breakIfDone = Expression.IfThen(
481 Expression.Equal(index, count),
482 Expression.Break(@break)
485 var incrementIndex = Expression.PreIncrementAssign(index);
490 Expression.Assign(applicable, Expression.Call(typeof(CallSiteOps), "GetRules", typeArgs, @this)),
491 Expression.Constant(null, applicable.Type)
494 Expression.Assign(count, Expression.ArrayLength(applicable)),
495 Expression.Assign(index, Expression.Constant(0)),
502 Expression.Convert(rule, typeof(object)),
503 Expression.Convert(originalRule, typeof(object))
507 Expression.Field(@this, "Target"),
524 //// Level 2 cache lookup
528 //// Any applicable rules in level 2 cache?
531 // var cache = CallSiteOps.GetRuleCache(@this);
533 var cache = Expression.Variable(typeof(RuleCache<T>), "cache");
539 Expression.Call(typeof(CallSiteOps), "GetRuleCache", typeArgs, @this)
543 // applicable = cache.GetRules();
548 Expression.Call(typeof(CallSiteOps), "GetCachedRules", typeArgs, cache)
552 // for (int i = 0, count = applicable.Length; i < count; i++) {
553 // @this.Target = rule = applicable[i];
556 // // Execute the rule
560 // result = rule(site, arg0);
565 // if (CallSiteOps.GetMatch(site)) {
567 // // Rule worked. Add it to level 1 cache
570 // CallSiteOps.AddRule(@this, rule);
571 // // and then move it to the front of the L2 cache
572 // CallSiteOps.MoveRule(cache, rule, index);
576 // // Rule didn't match, try the next one
577 // CallSiteOps.ClearMatch(site);
582 // L2 invokeRule is different (no onMatch)
583 if (@return.Type == typeof(void)) {
584 invokeRule = Expression.Block(
585 Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params)),
588 Expression.Return(@return)
592 invokeRule = Expression.Block(
593 Expression.Assign(result, Expression.Invoke(rule, new TrueReadOnlyCollection<Expression>(@params))),
596 Expression.Return(@return, result)
601 var tryRule = Expression.TryFinally(
606 Expression.Call(typeof(CallSiteOps), "AddRule", typeArgs, @this, rule),
607 Expression.Call(typeof(CallSiteOps), "MoveRule", typeArgs, cache, rule, index)
612 getRule = Expression.Assign(
613 Expression.Field(@this, "Target"),
614 Expression.Assign(rule, Expression.ArrayAccess(applicable, index))
617 body.Add(Expression.Assign(index, Expression.Constant(0)));
618 body.Add(Expression.Assign(count, Expression.ArrayLength(applicable)));
634 //// Miss on Level 0, 1 and 2 caches. Create new rule
638 body.Add(Expression.Assign(rule, Expression.Constant(null, rule.Type)));
640 //var args = new object[] { arg0, arg1, ... };
641 var args = Expression.Variable(typeof(object[]), "args");
646 Expression.NewArrayInit(typeof(object), arguments.Map(p => Convert(p, typeof(object))))
651 // @this.Target = originalRule;
652 // rule = @this.Target = @this.Binder.BindDelegate(@this, args);
655 // // Execute the rule on the matchmaker site
659 // %(setResult)s ruleTarget(site, %(args)s);
666 // // The rule worked. Add it to level 1 cache.
668 // CallSiteOps.AddRule(@this, rule);
672 // // Rule we got back didn't work, try another one
676 Expression setOldTarget = Expression.Assign(
677 Expression.Field(@this, "Target"),
681 getRule = Expression.Assign(
682 Expression.Field(@this, "Target"),
689 Expression.Property(@this, "Binder"),
696 tryRule = Expression.TryFinally(
700 Expression.Call(typeof(CallSiteOps), "AddRule", typeArgs, @this, rule)
706 Expression.Block(setOldTarget, getRule, tryRule, resetMatch),
711 body.Add(Expression.Default(@return.Type));
713 var lambda = Expression.Lambda<T>(
717 new ReadOnlyCollection<ParameterExpression>(vars),
718 new ReadOnlyCollection<Expression>(body)
722 true, // always compile the rules with tail call optimization
723 new ReadOnlyCollection<ParameterExpression>(@params)
726 // Need to compile with forceDynamic because T could be invisible,
727 // or one of the argument types could be invisible
728 return lambda.Compile();
731 private static Expression Convert(Expression arg, Type type) {
732 if (TypeUtils.AreReferenceAssignable(type, arg.Type)) {
735 return Expression.Convert(arg, type);