Skip to content

Commit 8f8c2d4

Browse files
authored
Use Linq to compile getters (#1706)
* Use Linq to compile getters * Switch to tiered compilation for getter * Remove Box cache from TryGetByReflection * Skip casting if TSelfType is compatible with property.get
1 parent 05f07f3 commit 8f8c2d4

File tree

1 file changed

+109
-27
lines changed

1 file changed

+109
-27
lines changed

Src/IronPython/Runtime/Binding/PythonGetMemberBinder.cs

Lines changed: 109 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Reflection;
1212
using System.Runtime.CompilerServices;
1313
using System.Text;
14+
using System.Threading;
1415

1516
using Microsoft.Scripting;
1617
using Microsoft.Scripting.Actions;
@@ -282,40 +283,130 @@ public object GetTypeObject(CallSite site, TSelfType target, CodeContext context
282283
}
283284
}
284285

285-
private class FastPropertyGet<TSelfType> : BuiltinBase<TSelfType> {
286+
/// <summary>
287+
/// Property Getter that will JIT compile a getter
288+
/// after 8 invocations, for faster gets.
289+
/// </summary>
290+
/// <typeparam name="TSelfType"></typeparam>
291+
private class TieredJitPropertyGet<TSelfType> {
292+
private const int ReflectionGetLimit = 8;
286293
private readonly Type _type;
287-
private readonly Func<object, object> _propGetter;
294+
private readonly MethodInfo _method;
295+
296+
private delegate bool TryGetProperty(TSelfType target, out object result);
297+
298+
private int _runCount;
299+
private TryGetProperty _jitTryGetProperty;
300+
private readonly CallInstruction _reflectionInvoke;
288301

289-
public FastPropertyGet(Type type, Func<object, object> propGetter) {
302+
public TieredJitPropertyGet(Type type, MethodInfo method) {
290303
_type = type;
291-
_propGetter = propGetter;
304+
_method = method;
305+
_reflectionInvoke = CallInstruction.Create(method, Array.Empty<ParameterInfo>());
292306
}
293307

294308
public object GetProperty(CallSite site, TSelfType target, CodeContext context) {
295-
if (target != null && target.GetType() == _type) {
296-
return _propGetter(target);
309+
if (_jitTryGetProperty == null) {
310+
if (Interlocked.Increment(ref _runCount) > ReflectionGetLimit) {
311+
_jitTryGetProperty = TryGetByJit(_method);
312+
}
297313
}
298314

315+
var tryGetProperty = _jitTryGetProperty ?? TryGetByReflection;
316+
if (tryGetProperty(target, out var result))
317+
return result;
318+
299319
return ((CallSite<Func<CallSite, TSelfType, CodeContext, object>>)site).Update(site, target, context);
300320
}
301321

302-
public object GetPropertyBool(CallSite site, TSelfType target, CodeContext context) {
322+
private bool TryGetByReflection(TSelfType target, out object result) {
303323
if (target != null && target.GetType() == _type) {
304-
return ScriptingRuntimeHelpers.BooleanToObject((bool)_propGetter(target));
324+
result = _reflectionInvoke.Invoke(target);
325+
return true;
305326
}
306327

307-
return ((CallSite<Func<CallSite, TSelfType, CodeContext, object>>)site).Update(site, target, context);
328+
result = default;
329+
return false;
308330
}
309331

310-
public object GetPropertyInt(CallSite site, TSelfType target, CodeContext context) {
311-
if (target != null && target.GetType() == _type) {
312-
return ScriptingRuntimeHelpers.Int32ToObject((int)_propGetter(target));
332+
private static TryGetProperty TryGetByJit(MethodInfo method) {
333+
return method.DeclaringType!.IsAssignableFrom(typeof(TSelfType))
334+
? TryGetByJitWithoutCast(method)
335+
: TryGetByJitRequiringCast(method);
336+
}
337+
338+
private static TryGetProperty TryGetByJitWithoutCast(MethodInfo method) {
339+
var input = Expression.Parameter(typeof(TSelfType));
340+
var output = Expression.Parameter(typeof(object).MakeByRefType());
341+
342+
var returnTarget = Expression.Label(typeof(bool));
343+
return Expression.Lambda<TryGetProperty>(
344+
Expression.Block(new Expression[] {
345+
346+
347+
Expression.IfThen(
348+
// if (input != null)
349+
Expression.NotEqual(input, Expression.Constant(null)),
350+
Expression.Block(
351+
// output = property.Invoke(input)
352+
Expression.Assign(output, Utils.Box(Expression.Call(input, method))),
353+
// return true
354+
Expression.Return(returnTarget, Expression.Constant(true)))
355+
),
356+
Expression.Label(returnTarget, Expression.Constant(false)),
357+
}),
358+
input,
359+
output
360+
).Compile();
361+
}
362+
363+
private static TryGetProperty TryGetByJitRequiringCast(MethodInfo method) {
364+
var input = Expression.Parameter(typeof(TSelfType));
365+
var output = Expression.Parameter(typeof(object).MakeByRefType());
366+
367+
var declaringType = method.DeclaringType!;
368+
var isStruct = declaringType.IsValueType;
369+
var isNullable = declaringType.IsGenericType &&
370+
declaringType.GetGenericTypeDefinition() == typeof(Nullable<>);
371+
372+
var tryCastResult = Expression.Variable(isStruct && !isNullable
373+
? typeof(Nullable<>).MakeGenericType(declaringType)
374+
: declaringType);
375+
var returnTarget = Expression.Label(typeof(bool));
376+
377+
Expression UnwrapNullable(Expression expression) {
378+
if (isStruct && !isNullable) {
379+
return Expression.Property(expression, nameof(Nullable<int>.Value));
380+
}
381+
382+
return expression;
313383
}
314384

315-
return ((CallSite<Func<CallSite, TSelfType, CodeContext, object>>)site).Update(site, target, context);
385+
return Expression.Lambda<TryGetProperty>(
386+
Expression.Block(new[] { tryCastResult }, new Expression[] {
387+
// var tryCastResult = input as T;
388+
Expression.Assign(
389+
tryCastResult,
390+
Expression.TypeAs(input, tryCastResult.Type)),
391+
Expression.IfThen(
392+
// if (tryCastResult != null)
393+
Expression.NotEqual(tryCastResult, Expression.Constant(null)),
394+
Expression.Block(
395+
// output = property.Invoke(tryCastResult)
396+
Expression.Assign(output, Utils.Box(Expression.Call(UnwrapNullable(tryCastResult), method))),
397+
// return true
398+
Expression.Return(returnTarget, Expression.Constant(true)))
399+
),
400+
Expression.Label(returnTarget, Expression.Constant(false)),
401+
}),
402+
input,
403+
output
404+
).Compile();
316405
}
317406
}
318407

408+
409+
319410
private Func<CallSite, TSelfType, CodeContext, object> MakeGetMemberTarget<TSelfType>(string name, object target, CodeContext context) {
320411
Type type = CompilerHelpers.GetType(target);
321412

@@ -391,18 +482,9 @@ private Func<CallSite, TSelfType, CodeContext, object> MakeGetMemberTarget<TSelf
391482
case TrackerTypes.Property:
392483
if (members.Count == 1) {
393484
PropertyTracker pt = (PropertyTracker)members[0];
394-
if (!pt.IsStatic && pt.GetIndexParameters().Length == 0) {
395-
MethodInfo prop = pt.GetGetMethod();
396-
ParameterInfo[] parameters;
397-
398-
if (prop != null && (parameters = prop.GetParameters()).Length == 0) {
399-
if (prop.ReturnType == typeof(bool)) {
400-
return new FastPropertyGet<TSelfType>(type, CallInstruction.Create(prop, parameters).Invoke).GetPropertyBool;
401-
} else if (prop.ReturnType == typeof(int)) {
402-
return new FastPropertyGet<TSelfType>(type, CallInstruction.Create(prop, parameters).Invoke).GetPropertyInt;
403-
} else {
404-
return new FastPropertyGet<TSelfType>(type, CallInstruction.Create(prop, parameters).Invoke).GetProperty;
405-
}
485+
if (!pt.IsStatic && pt.GetIndexParameters() is { Length: 0}) {
486+
if (pt.GetGetMethod() is {} prop && prop.GetParameters() is { Length: 0 }) {
487+
return new TieredJitPropertyGet<TSelfType>(type, prop).GetProperty;
406488
}
407489
}
408490
}
@@ -580,7 +662,7 @@ internal static DynamicMetaObject FallbackWorker(PythonContext context, DynamicM
580662
Type limitType = self.GetLimitType();
581663

582664
if (limitType == typeof(DynamicNull) || PythonBinder.IsPythonType(limitType) || PythonBinder.IsPythonSupportingType(limitType)) {
583-
// look up in the PythonType so that we can
665+
// look up in the PythonType so that we can
584666
// get our custom method names (e.g. string.startswith)
585667
PythonType argType = DynamicHelpers.GetPythonTypeFromType(limitType);
586668

@@ -608,7 +690,7 @@ internal static DynamicMetaObject FallbackWorker(PythonContext context, DynamicM
608690

609691
if (extMethods != null) {
610692
// try again w/ the extension method binder
611-
res = extMethods.GetBinder(context).GetMember(name, self, resolverFactory, isNoThrow, errorSuggestion);
693+
res = extMethods.GetBinder(context).GetMember(name, self, resolverFactory, isNoThrow, errorSuggestion);
612694
}
613695

614696
// and add any restrictions (we need an empty restriction even if it's an error so later adds work)

0 commit comments

Comments
 (0)