/* **************************************************************************** * * 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_TASKS using System.Threading.Tasks; #endif #if FEATURE_CORE_DLR using System.Linq.Expressions; #else using Microsoft.Scripting.Ast; #endif using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Dynamic; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Scripting.Generation; using Microsoft.Scripting.Interpreter; using Microsoft.Scripting.Runtime; #if !FEATURE_DYNAMIC_EXPRESSION_VISITOR #if FEATURE_CORE_DLR namespace System.Linq.Expressions { #else namespace Microsoft.Scripting.Ast { #endif public abstract class DynamicExpressionVisitor : ExpressionVisitor { } } #endif namespace Microsoft.Scripting.Utils { using AstUtils = Microsoft.Scripting.Ast.Utils; public static class DynamicUtils { /// /// Returns the list of expressions represented by the instances. /// /// An array of instances to extract expressions from. /// The array of expressions. public static Expression[] GetExpressions(DynamicMetaObject[] objects) { ContractUtils.RequiresNotNull(objects, "objects"); Expression[] res = new Expression[objects.Length]; for (int i = 0; i < objects.Length; i++) { DynamicMetaObject mo = objects[i]; res[i] = mo != null ? mo.Expression : null; } return res; } /// /// Creates an instance of for a runtime value and the expression that represents it during the binding process. /// /// The runtime value to be represented by the . /// An expression to represent this during the binding process. /// The new instance of . public static DynamicMetaObject ObjectToMetaObject(object argValue, Expression parameterExpression) { IDynamicMetaObjectProvider ido = argValue as IDynamicMetaObjectProvider; if (ido != null) { return ido.GetMetaObject(parameterExpression); } else { return new DynamicMetaObject(parameterExpression, BindingRestrictions.Empty, argValue); } } /// /// Produces an interpreted binding using the given binder which falls over to a compiled /// binding after hitCount tries. /// /// This method should be called whenever an interpreted binding is required. Sometimes it will /// return a compiled binding if a previous binding was produced and it's hit count was exhausted. /// In this case the binder will not be called back for a new binding - the previous one will /// be used. /// /// The delegate type being used for the call site /// The binder used for the call site /// The number of calls before the binder should switch to a compiled mode. /// The arguments that are passed for the binding (as received in a BindDelegate call) /// A delegate which represents the interpreted binding. public static T/*!*/ LightBind(this DynamicMetaObjectBinder/*!*/ binder, object[]/*!*/ args, int compilationThreshold) where T : class { ContractUtils.RequiresNotNull(binder, "binder"); ContractUtils.RequiresNotNull(args, "args"); return GenericInterpretedBinder.Instance.Bind(binder, compilationThreshold < 0 ? LightCompiler.DefaultCompilationThreshold : compilationThreshold, args); } private class GenericInterpretedBinder where T : class { public static GenericInterpretedBinder/*!*/ Instance = new GenericInterpretedBinder(); private readonly ReadOnlyCollection/*!*/ _parameters; private readonly Expression/*!*/ _updateExpression; private GenericInterpretedBinder() { var invokeMethod = typeof(T).GetMethod("Invoke"); var methodParams = invokeMethod.GetParameters(); ReadOnlyCollectionBuilder prms = new ReadOnlyCollectionBuilder(methodParams.Length); ReadOnlyCollectionBuilder invokePrms = new ReadOnlyCollectionBuilder(methodParams.Length); for (int i = 0; i < methodParams.Length; i++) { var param = Expression.Parameter(methodParams[i].ParameterType); if (i == 0) { invokePrms.Add(Expression.Convert(param, typeof(CallSite))); } else { invokePrms.Add(param); } prms.Add(param); } _parameters = prms.ToReadOnlyCollection(); _updateExpression = Expression.Block( Expression.Label(CallSiteBinder.UpdateLabel), Expression.Invoke( Expression.Property( invokePrms[0], typeof(CallSite).GetDeclaredProperty("Update") ), invokePrms.ToReadOnlyCollection() ) ); } public T/*!*/ Bind(DynamicMetaObjectBinder/*!*/ binder, int compilationThreshold, object[] args) { if (CachedBindingInfo.LastInterpretedFailure != null && CachedBindingInfo.LastInterpretedFailure.Binder == binder) { // we failed the rule because we have a compiled target available, return the compiled target Debug.Assert(CachedBindingInfo.LastInterpretedFailure.CompiledTarget != null); var res = CachedBindingInfo.LastInterpretedFailure.CompiledTarget; CachedBindingInfo.LastInterpretedFailure = null; return res; } // we haven't produced a rule yet.... var bindingInfo = new CachedBindingInfo(binder, compilationThreshold); var targetMO = DynamicMetaObject.Create(args[0], _parameters[1]); // 1 is skipping CallSite DynamicMetaObject[] argsMO = new DynamicMetaObject[args.Length - 1]; for (int i = 0; i < argsMO.Length; i++) { argsMO[i] = DynamicMetaObject.Create(args[i + 1], _parameters[i + 2]); } var binding = binder.Bind(targetMO, argsMO); return CreateDelegate(binding, bindingInfo); } private T/*!*/ CreateDelegate(DynamicMetaObject/*!*/ binding, CachedBindingInfo/*!*/ bindingInfo) { return Compile(binding, bindingInfo).LightCompile(Int32.MaxValue); } private Expression/*!*/ Compile(DynamicMetaObject/*!*/ obj, CachedBindingInfo/*!*/ bindingInfo) { var restrictions = obj.Restrictions.ToExpression(); var body = Expression.Condition( new InterpretedRuleHitCheckExpression(restrictions, bindingInfo), AstUtils.Convert(obj.Expression, _updateExpression.Type), _updateExpression ); var res = Expression.Lambda( body, "CallSite.Target", true, // always compile the rules with tail call optimization _parameters ); bindingInfo.Target = res; return res; } /// /// Expression which reduces to the normal test but under the interpreter adds a count down /// check which enables compiling when the count down is reached. /// class InterpretedRuleHitCheckExpression : Expression, IInstructionProvider { private readonly Expression/*!*/ _test; private readonly CachedBindingInfo/*!*/ _bindingInfo; private static readonly MethodInfo InterpretedCallSiteTest = typeof(ScriptingRuntimeHelpers).GetMethod("InterpretedCallSiteTest"); public InterpretedRuleHitCheckExpression(Expression/*!*/ test, CachedBindingInfo/*!*/ bindingInfo) { Assert.NotNull(test, bindingInfo); _test = test; _bindingInfo = bindingInfo; } public override Expression Reduce() { return _test; } protected override Expression VisitChildren(ExpressionVisitor visitor) { var test = visitor.Visit(_test); if (test != _test) { return new InterpretedRuleHitCheckExpression(test, _bindingInfo); } return this; } public override bool CanReduce { get { return true; } } public override ExpressionType NodeType { get { return ExpressionType.Extension; } } public override Type Type { get { return typeof(bool); } } #region IInstructionProvider Members public void AddInstructions(LightCompiler compiler) { compiler.Compile(_test); compiler.Instructions.EmitLoad(_bindingInfo); compiler.EmitCall(InterpretedCallSiteTest); } #endregion } } } /// /// Base class for storing information about the binding that a specific rule is applicable for. /// /// We have a derived generic class but this class enables us to refer to it w/o having the /// generic type information around. /// /// This class tracks both the count down to when we should compile. When we compile we /// take the Expression[T] that was used before and compile it. While this is happening /// we continue to allow the interpreted code to run. When the compilation is complete we /// store a thread static which tells us what binding failed and the current rule is no /// longer functional. Finally the language binder will call us again and we'll retrieve /// and return the compiled overload. /// abstract class CachedBindingInfo { public readonly DynamicMetaObjectBinder/*!*/ Binder; public int CompilationThreshold; public CachedBindingInfo(DynamicMetaObjectBinder binder, int compilationThreshold) { Binder = binder; CompilationThreshold = compilationThreshold; } public abstract bool CheckCompiled(); } class CachedBindingInfo : CachedBindingInfo where T : class { public T CompiledTarget; public Expression Target; [ThreadStatic] public static CachedBindingInfo LastInterpretedFailure; public CachedBindingInfo(DynamicMetaObjectBinder binder, int compilationThreshold) : base(binder, compilationThreshold) { } public override bool CheckCompiled() { if (Target != null) { // start compiling the target if no one else has var lambda = Interlocked.Exchange(ref Target, null); if (lambda != null) { #if FEATURE_TASKS new Task(() => { CompiledTarget = lambda.Compile(); }).Start(); #else ThreadPool.QueueUserWorkItem(x => { CompiledTarget = lambda.Compile(); }); #endif } } if (CompiledTarget != null) { LastInterpretedFailure = this; return false; } return true; } } }