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 System
.Threading
.Tasks
;
21 using System
.Linq
.Expressions
;
23 using Microsoft
.Scripting
.Ast
;
27 using System
.Collections
.ObjectModel
;
28 using System
.Diagnostics
;
30 using System
.Reflection
;
31 using System
.Runtime
.CompilerServices
;
32 using System
.Threading
;
33 using Microsoft
.Scripting
.Generation
;
34 using Microsoft
.Scripting
.Interpreter
;
35 using Microsoft
.Scripting
.Runtime
;
37 #if !FEATURE_DYNAMIC_EXPRESSION_VISITOR
39 namespace System
.Linq
.Expressions
{
41 namespace Microsoft
.Scripting
.Ast
{
43 public abstract class DynamicExpressionVisitor
: ExpressionVisitor
{
48 namespace Microsoft
.Scripting
.Utils
{
49 using AstUtils
= Microsoft
.Scripting
.Ast
.Utils
;
51 public static class DynamicUtils
{
53 /// Returns the list of expressions represented by the <see cref="DynamicMetaObject"/> instances.
55 /// <param name="objects">An array of <see cref="DynamicMetaObject"/> instances to extract expressions from.</param>
56 /// <returns>The array of expressions.</returns>
57 public static Expression
[] GetExpressions(DynamicMetaObject
[] objects
) {
58 ContractUtils
.RequiresNotNull(objects
, "objects");
60 Expression
[] res
= new Expression
[objects
.Length
];
61 for (int i
= 0; i
< objects
.Length
; i
++) {
62 DynamicMetaObject mo
= objects
[i
];
63 res
[i
] = mo
!= null ? mo
.Expression
: null;
70 /// Creates an instance of <see cref="DynamicMetaObject"/> for a runtime value and the expression that represents it during the binding process.
72 /// <param name="argValue">The runtime value to be represented by the <see cref="DynamicMetaObject"/>.</param>
73 /// <param name="parameterExpression">An expression to represent this <see cref="DynamicMetaObject"/> during the binding process.</param>
74 /// <returns>The new instance of <see cref="DynamicMetaObject"/>.</returns>
75 public static DynamicMetaObject
ObjectToMetaObject(object argValue
, Expression parameterExpression
) {
76 IDynamicMetaObjectProvider ido
= argValue
as IDynamicMetaObjectProvider
;
78 return ido
.GetMetaObject(parameterExpression
);
80 return new DynamicMetaObject(parameterExpression
, BindingRestrictions
.Empty
, argValue
);
85 /// Produces an interpreted binding using the given binder which falls over to a compiled
86 /// binding after hitCount tries.
88 /// This method should be called whenever an interpreted binding is required. Sometimes it will
89 /// return a compiled binding if a previous binding was produced and it's hit count was exhausted.
90 /// In this case the binder will not be called back for a new binding - the previous one will
93 /// <typeparam name="T">The delegate type being used for the call site</typeparam>
94 /// <param name="binder">The binder used for the call site</param>
95 /// <param name="compilationThreshold">The number of calls before the binder should switch to a compiled mode.</param>
96 /// <param name="args">The arguments that are passed for the binding (as received in a BindDelegate call)</param>
97 /// <returns>A delegate which represents the interpreted binding.</returns>
98 public static T
/*!*/ LightBind
<T
>(this DynamicMetaObjectBinder
/*!*/ binder
, object[]/*!*/ args
, int compilationThreshold
) where T
: class {
99 ContractUtils
.RequiresNotNull(binder
, "binder");
100 ContractUtils
.RequiresNotNull(args
, "args");
102 return GenericInterpretedBinder
<T
>.Instance
.Bind(binder
, compilationThreshold
< 0 ? LightCompiler
.DefaultCompilationThreshold
: compilationThreshold
, args
);
105 private class GenericInterpretedBinder
<T
> where T
: class {
106 public static GenericInterpretedBinder
<T
>/*!*/ Instance
= new GenericInterpretedBinder
<T
>();
107 private readonly ReadOnlyCollection
<ParameterExpression
>/*!*/ _parameters
;
108 private readonly Expression
/*!*/ _updateExpression
;
110 private GenericInterpretedBinder() {
111 var invokeMethod
= typeof(T
).GetMethod("Invoke");
112 var methodParams
= invokeMethod
.GetParameters();
114 ReadOnlyCollectionBuilder
<ParameterExpression
> prms
= new ReadOnlyCollectionBuilder
<ParameterExpression
>(methodParams
.Length
);
115 ReadOnlyCollectionBuilder
<Expression
> invokePrms
= new ReadOnlyCollectionBuilder
<Expression
>(methodParams
.Length
);
116 for (int i
= 0; i
< methodParams
.Length
; i
++) {
117 var param
= Expression
.Parameter(methodParams
[i
].ParameterType
);
119 invokePrms
.Add(Expression
.Convert(param
, typeof(CallSite
<T
>)));
121 invokePrms
.Add(param
);
126 _parameters
= prms
.ToReadOnlyCollection();
128 _updateExpression
= Expression
.Block(
129 Expression
.Label(CallSiteBinder
.UpdateLabel
),
133 typeof(CallSite
<T
>).GetDeclaredProperty("Update")
135 invokePrms
.ToReadOnlyCollection()
140 public T
/*!*/ Bind(DynamicMetaObjectBinder
/*!*/ binder
, int compilationThreshold
, object[] args
) {
141 if (CachedBindingInfo
<T
>.LastInterpretedFailure
!= null && CachedBindingInfo
<T
>.LastInterpretedFailure
.Binder
== binder
) {
142 // we failed the rule because we have a compiled target available, return the compiled target
143 Debug
.Assert(CachedBindingInfo
<T
>.LastInterpretedFailure
.CompiledTarget
!= null);
144 var res
= CachedBindingInfo
<T
>.LastInterpretedFailure
.CompiledTarget
;
145 CachedBindingInfo
<T
>.LastInterpretedFailure
= null;
149 // we haven't produced a rule yet....
150 var bindingInfo
= new CachedBindingInfo
<T
>(binder
, compilationThreshold
);
152 var targetMO
= DynamicMetaObject
.Create(args
[0], _parameters
[1]); // 1 is skipping CallSite
153 DynamicMetaObject
[] argsMO
= new DynamicMetaObject
[args
.Length
- 1];
154 for (int i
= 0; i
< argsMO
.Length
; i
++) {
155 argsMO
[i
] = DynamicMetaObject
.Create(args
[i
+ 1], _parameters
[i
+ 2]);
157 var binding
= binder
.Bind(targetMO
, argsMO
);
159 return CreateDelegate(binding
, bindingInfo
);
162 private T
/*!*/ CreateDelegate(DynamicMetaObject
/*!*/ binding
, CachedBindingInfo
<T
>/*!*/ bindingInfo
) {
163 return Compile(binding
, bindingInfo
).LightCompile(Int32
.MaxValue
);
166 private Expression
<T
>/*!*/ Compile(DynamicMetaObject
/*!*/ obj
, CachedBindingInfo
<T
>/*!*/ bindingInfo
) {
167 var restrictions
= obj
.Restrictions
.ToExpression();
169 var body
= Expression
.Condition(
170 new InterpretedRuleHitCheckExpression(restrictions
, bindingInfo
),
171 AstUtils
.Convert(obj
.Expression
, _updateExpression
.Type
),
175 var res
= Expression
.Lambda
<T
>(
178 true, // always compile the rules with tail call optimization
182 bindingInfo
.Target
= res
;
187 /// Expression which reduces to the normal test but under the interpreter adds a count down
188 /// check which enables compiling when the count down is reached.
190 class InterpretedRuleHitCheckExpression
: Expression
, IInstructionProvider
{
191 private readonly Expression
/*!*/ _test
;
192 private readonly CachedBindingInfo
/*!*/ _bindingInfo
;
194 private static readonly MethodInfo InterpretedCallSiteTest
= typeof(ScriptingRuntimeHelpers
).GetMethod("InterpretedCallSiteTest");
195 public InterpretedRuleHitCheckExpression(Expression
/*!*/ test
, CachedBindingInfo
/*!*/ bindingInfo
) {
196 Assert
.NotNull(test
, bindingInfo
);
199 _bindingInfo
= bindingInfo
;
202 public override Expression
Reduce() {
206 protected override Expression
VisitChildren(ExpressionVisitor visitor
) {
207 var test
= visitor
.Visit(_test
);
209 return new InterpretedRuleHitCheckExpression(test
, _bindingInfo
);
214 public override bool CanReduce
{
218 public override ExpressionType NodeType
{
219 get { return ExpressionType.Extension; }
222 public override Type Type
{
223 get { return typeof(bool); }
226 #region IInstructionProvider Members
228 public void AddInstructions(LightCompiler compiler
) {
229 compiler
.Compile(_test
);
230 compiler
.Instructions
.EmitLoad(_bindingInfo
);
231 compiler
.EmitCall(InterpretedCallSiteTest
);
241 /// Base class for storing information about the binding that a specific rule is applicable for.
243 /// We have a derived generic class but this class enables us to refer to it w/o having the
244 /// generic type information around.
246 /// This class tracks both the count down to when we should compile. When we compile we
247 /// take the Expression[T] that was used before and compile it. While this is happening
248 /// we continue to allow the interpreted code to run. When the compilation is complete we
249 /// store a thread static which tells us what binding failed and the current rule is no
250 /// longer functional. Finally the language binder will call us again and we'll retrieve
251 /// and return the compiled overload.
253 abstract class CachedBindingInfo
{
254 public readonly DynamicMetaObjectBinder
/*!*/ Binder
;
255 public int CompilationThreshold
;
257 public CachedBindingInfo(DynamicMetaObjectBinder binder
, int compilationThreshold
) {
259 CompilationThreshold
= compilationThreshold
;
262 public abstract bool CheckCompiled();
265 class CachedBindingInfo
<T
> : CachedBindingInfo where T
: class {
266 public T CompiledTarget
;
267 public Expression
<T
> Target
;
270 public static CachedBindingInfo
<T
> LastInterpretedFailure
;
272 public CachedBindingInfo(DynamicMetaObjectBinder binder
, int compilationThreshold
)
273 : base(binder
, compilationThreshold
) {
276 public override bool CheckCompiled() {
277 if (Target
!= null) {
278 // start compiling the target if no one else has
279 var lambda
= Interlocked
.Exchange(ref Target
, null);
280 if (lambda
!= null) {
282 new Task(() => { CompiledTarget = lambda.Compile(); }
).Start();
284 ThreadPool
.QueueUserWorkItem(x
=> { CompiledTarget = lambda.Compile(); }
);
289 if (CompiledTarget
!= null) {
290 LastInterpretedFailure
= this;