File: Microsoft\Scripting\Actions\CallSiteBinder.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
/* ****************************************************************************
 *
 * 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;
        }
    }
}