/* ****************************************************************************
*
* 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 !FEATURE_CORE_DLR
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#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 {
///
/// Class responsible for runtime binding of the dynamic operations on the dynamic call site.
///
public abstract class CallSiteBinder {
private static readonly LabelTarget _updateLabel = Expression.Label("CallSiteBinder.UpdateLabel");
///
/// The Level 2 cache - all rules produced for the same binder.
///
internal Dictionary Cache;
///
/// Initializes a new instance of the class.
///
protected CallSiteBinder() {
}
///
/// 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.
///
public static LabelTarget UpdateLabel {
get { return _updateLabel; }
}
private sealed class LambdaSignature where T : class {
internal static readonly LambdaSignature Instance = new LambdaSignature();
internal readonly ReadOnlyCollection 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(@params);
ReturnLabel = Expression.Label(invoke.GetReturnType());
}
}
///
/// Performs the runtime binding of the dynamic operation on a set of arguments.
///
/// An array of arguments to the dynamic operation.
/// The array of instances that represent the parameters of the call site in the binding process.
/// A LabelTarget used to return the result of the dynamic binding.
///
/// 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 for the new argument types.
///
public abstract Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel);
///
/// 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.
///
/// The target type of the CallSite.
/// The CallSite the bind is being performed for.
/// The arguments for the binder.
/// A new delegate which replaces the CallSite Target.
public virtual T BindDelegate(CallSite site, object[] args) where T : class {
return null;
}
internal T BindCore(CallSite 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.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 && !ANDROID && !WP75
// 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 e = Stitch(binding, signature);
T newRule = e.Compile();
CacheTarget(newRule);
return newRule;
}
///
/// Adds a target to the cache of known targets. The cached targets will
/// be scanned before calling BindDelegate to produce the new rule.
///
/// The type of target being added.
/// The target delegate to be added to the cache.
protected void CacheTarget(T target) where T : class {
GetRuleCache().AddRule(target);
}
private static Expression Stitch(Expression binding, LambdaSignature signature) where T : class {
Type siteType = typeof(CallSite);
var body = new ReadOnlyCollectionBuilder(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).GetProperty("Update")
),
new TrueReadOnlyCollection(@params)
)
)
)
);
return new Expression(
Expression.Block(body),
"CallSite.Target",
true, // always compile the rules with tail call optimization
new TrueReadOnlyCollection(@params)
);
}
internal RuleCache GetRuleCache() where T : class {
// make sure we have cache.
if (Cache == null) {
Interlocked.CompareExchange(ref Cache, new Dictionary(), null);
}
object ruleCache;
var cache = Cache;
lock (cache) {
if (!cache.TryGetValue(typeof(T), out ruleCache)) {
cache[typeof(T)] = ruleCache = new RuleCache();
}
}
RuleCache result = ruleCache as RuleCache;
Debug.Assert(result != null);
return result;
}
}
}