|
11 | 11 | using System.Reflection; |
12 | 12 | using System.Runtime.CompilerServices; |
13 | 13 | using System.Text; |
| 14 | +using System.Threading; |
14 | 15 |
|
15 | 16 | using Microsoft.Scripting; |
16 | 17 | using Microsoft.Scripting.Actions; |
@@ -282,40 +283,130 @@ public object GetTypeObject(CallSite site, TSelfType target, CodeContext context |
282 | 283 | } |
283 | 284 | } |
284 | 285 |
|
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; |
286 | 293 | 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; |
288 | 301 |
|
289 | | - public FastPropertyGet(Type type, Func<object, object> propGetter) { |
| 302 | + public TieredJitPropertyGet(Type type, MethodInfo method) { |
290 | 303 | _type = type; |
291 | | - _propGetter = propGetter; |
| 304 | + _method = method; |
| 305 | + _reflectionInvoke = CallInstruction.Create(method, Array.Empty<ParameterInfo>()); |
292 | 306 | } |
293 | 307 |
|
294 | 308 | 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 | + } |
297 | 313 | } |
298 | 314 |
|
| 315 | + var tryGetProperty = _jitTryGetProperty ?? TryGetByReflection; |
| 316 | + if (tryGetProperty(target, out var result)) |
| 317 | + return result; |
| 318 | + |
299 | 319 | return ((CallSite<Func<CallSite, TSelfType, CodeContext, object>>)site).Update(site, target, context); |
300 | 320 | } |
301 | 321 |
|
302 | | - public object GetPropertyBool(CallSite site, TSelfType target, CodeContext context) { |
| 322 | + private bool TryGetByReflection(TSelfType target, out object result) { |
303 | 323 | if (target != null && target.GetType() == _type) { |
304 | | - return ScriptingRuntimeHelpers.BooleanToObject((bool)_propGetter(target)); |
| 324 | + result = _reflectionInvoke.Invoke(target); |
| 325 | + return true; |
305 | 326 | } |
306 | 327 |
|
307 | | - return ((CallSite<Func<CallSite, TSelfType, CodeContext, object>>)site).Update(site, target, context); |
| 328 | + result = default; |
| 329 | + return false; |
308 | 330 | } |
309 | 331 |
|
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; |
313 | 383 | } |
314 | 384 |
|
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(); |
316 | 405 | } |
317 | 406 | } |
318 | 407 |
|
| 408 | + |
| 409 | + |
319 | 410 | private Func<CallSite, TSelfType, CodeContext, object> MakeGetMemberTarget<TSelfType>(string name, object target, CodeContext context) { |
320 | 411 | Type type = CompilerHelpers.GetType(target); |
321 | 412 |
|
@@ -391,18 +482,9 @@ private Func<CallSite, TSelfType, CodeContext, object> MakeGetMemberTarget<TSelf |
391 | 482 | case TrackerTypes.Property: |
392 | 483 | if (members.Count == 1) { |
393 | 484 | 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; |
406 | 488 | } |
407 | 489 | } |
408 | 490 | } |
@@ -580,7 +662,7 @@ internal static DynamicMetaObject FallbackWorker(PythonContext context, DynamicM |
580 | 662 | Type limitType = self.GetLimitType(); |
581 | 663 |
|
582 | 664 | 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 |
584 | 666 | // get our custom method names (e.g. string.startswith) |
585 | 667 | PythonType argType = DynamicHelpers.GetPythonTypeFromType(limitType); |
586 | 668 |
|
@@ -608,7 +690,7 @@ internal static DynamicMetaObject FallbackWorker(PythonContext context, DynamicM |
608 | 690 |
|
609 | 691 | if (extMethods != null) { |
610 | 692 | // 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); |
612 | 694 | } |
613 | 695 |
|
614 | 696 | // and add any restrictions (we need an empty restriction even if it's an error so later adds work) |
|
0 commit comments