/
CallSiteBinder.cs
225 lines (194 loc) · 8.54 KB
/
CallSiteBinder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using static System.Linq.Expressions.CachedReflectionInfo;
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Class responsible for runtime binding of the dynamic operations on the dynamic call site.
/// </summary>
public abstract class CallSiteBinder
{
/// <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; } = Expression.Label("CallSiteBinder.UpdateLabel");
private sealed class LambdaSignature<T> where T : class
{
private static LambdaSignature<T>? s_instance;
internal static LambdaSignature<T> Instance => s_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 System.Linq.Expressions.Error.TypeParameterIsNotDelegate(target);
}
MethodInfo invoke = target.GetInvokeMethod();
ParameterInfo[] pis = invoke.GetParametersCached();
if (pis[0].ParameterType != typeof(CallSite))
{
throw System.Linq.Expressions.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 the 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;
}
[RequiresDynamicCode(Expression.NewArrayRequiresDynamicCode)]
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 System.Linq.Expressions.Error.NoOrInvalidRuleProduced();
}
//
// finally produce the new rule if we need to
//
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(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(
CallSiteOps_SetNotMatched,
site
),
Expression.Default(signature.ReturnLabel.Type),
Expression.Invoke(
Expression.Property(
Expression.Convert(site, siteType),
typeof(CallSite<T>).GetProperty(nameof(CallSite<T>.Update))!
),
@params
)
)
)
);
return Expression.Lambda<T>(
Expression.Block(body),
CallSite.CallSiteTargetMethodName,
true, // always compile the rules with tail call optimization
@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;
}
}
}