Pārlūkot izejas kodu

Optimize interop (#519)

Marko Lahma 7 gadi atpakaļ
vecāks
revīzija
8bbd390486

+ 278 - 0
Jint.Benchmark/InteropBenchmark.cs

@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using BenchmarkDotNet.Attributes;
+using Jint.Native;
+using Jint.Native.Array;
+
+namespace Jint.Benchmark
+{
+    [MemoryDiagnoser]
+    public class InteropBenchmark
+    {
+        private const int OperationsPerInvoke = 1_000;
+
+        public class Person
+        {
+            public string Name { get; set; }
+        }
+
+        private Engine _engine;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            _engine = new Engine(cfg => cfg.AllowClr(
+                    typeof(Person).GetTypeInfo().Assembly,
+                    typeof(Console).GetTypeInfo().Assembly,
+                    typeof(System.IO.File).GetTypeInfo().Assembly))
+                .SetValue("log", new Action<object>(Console.WriteLine))
+                .SetValue("assert", new Action<bool>(x => { }))
+                .SetValue("equal", new Action<object, object>((x, y) => { }));
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void DelegatesCanBeSet()
+        {
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("square", new Func<double, double>(x => x * x));
+                _engine.Execute("assert(square(10) === 100);");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void ExtraParametersAreIgnored()
+        {
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("passNumber", new Func<int, int>(x => x));
+                _engine.Execute("assert(passNumber(123,'test',{},[],null) === 123);");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void GetObjectProperties()
+        {
+            var p = new Person
+            {
+                Name = "Mickey Mouse"
+            };
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("p", p);
+                _engine.Execute("assert(p.Name === 'Mickey Mouse');");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void InvokeObjectMethods()
+        {
+            var p = new Person
+            {
+                Name = "Mickey Mouse"
+            };
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("p", p);
+                _engine.Execute(@"assert(p.ToString() === 'Mickey Mouse');");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void SetObjectProperties()
+        {
+            var p = new Person
+            {
+                Name = "Mickey Mouse"
+            };
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("p", p);
+                _engine.Execute("p.Name = 'Donald Duck'; assert(p.Name === 'Donald Duck');");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void GetIndexUsingStringKey()
+        {
+            var dictionary = new Dictionary<string, Person>();
+            dictionary.Add("person1", new Person {Name = "Mickey Mouse"});
+            dictionary.Add("person2", new Person {Name = "Goofy"});
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("dictionary", dictionary);
+                _engine.Execute("assert(dictionary['person1'].Name === 'Mickey Mouse'); assert(dictionary['person2'].Name === 'Goofy');");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void GenericMethods()
+        {
+            var dictionary = new Dictionary<int, string>
+            {
+                {1, "Mickey Mouse"}
+            };
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("dictionary", dictionary);
+                _engine.Execute("dictionary.Clear(); dictionary.Add(2, 'Goofy'); assert(dictionary[2] === 'Goofy');");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void MultiGenericTypes()
+        {
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.Execute(@"
+                var type = System.Collections.Generic.Dictionary(System.Int32, System.String);
+                var dictionary = new type();
+                dictionary.Add(1, 'Mickey Mouse');
+                dictionary.Add(2, 'Goofy');
+                assert(dictionary[2] === 'Goofy');
+            ");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void IndexOnList()
+        {
+            var list = new List<object>(2);
+            list.Add("Mickey Mouse");
+            list.Add("Goofy");
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("list", list);
+                _engine.Execute("list[1] = 'Donald Duck'; assert(list[1] === 'Donald Duck');");
+            }
+        }
+
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void EcmaValuesAreAutomaticallyConvertedWhenSetInPoco()
+        {
+            var p = new Person
+            {
+                Name = "foo",
+            };
+
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("p", p);
+                _engine.Execute(@"
+                assert(p.Name === 'foo');
+                assert(p.Age === 0);
+                p.Name = 'bar';
+                p.Age = 10;
+            ");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void Trim()
+        {
+            var p = new Person
+            {
+                Name = "Mickey Mouse "
+            };
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("p", p);
+                _engine.Execute(@"
+                assert(p.Name === 'Mickey Mouse ');
+                p.Name = p.Name.trim();
+                assert(p.Name === 'Mickey Mouse');
+            ");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void MathFloor()
+        {
+            var p = new Person();
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("p", p);
+                _engine.Execute("p.Age = Math.floor(1.6); assert(p.Age === 1);");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void DelegateAsFunction()
+        {
+            var even = new Func<int, bool>(x => x % 2 == 0);
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("even", even);
+                _engine.Execute("assert(even(2) === true);");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void ConvertArrayToArrayInstance()
+        {
+            var ints = new[] {1, 2, 3, 4, 5, 6};
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine
+                    .SetValue("values", ints)
+                    .Execute("values.filter(function(x){ return x % 2 == 0; })");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void ConvertListsToArrayInstance()
+        {
+            var ints = new List<object> {1, 2, 3, 4, 5, 6};
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine
+                    .SetValue("values", ints)
+                    .Execute("new Array(values).filter(function(x){ return x % 2 == 0; })");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void ConvertArrayInstanceToArray()
+        {
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.Execute("'[email protected]'.split('@');");
+            }
+        }
+
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void LoopWithNativeEnumerator()
+        {
+            JsValue Adder(JsValue argValue)
+            {
+                ArrayInstance args = argValue.AsArray();
+                double sum = 0;
+                foreach (var item in args)
+                {
+                    if (item.IsNumber())
+                    {
+                        sum += item.AsNumber();
+                    }
+                }
+
+                return sum;
+            }
+
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.SetValue("getSum", new Func<JsValue, JsValue>(Adder));
+                _engine.Execute("getSum([1,2,3]);");
+            }
+        }
+    }
+}

+ 3 - 0
Jint/Engine.cs

@@ -83,6 +83,9 @@ namespace Jint
             { typeof(System.Text.RegularExpressions.Regex), (Engine engine, object v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "") }
         };
 
+        internal readonly Dictionary<(Type, string), Func<Engine, object, PropertyDescriptor>> ClrPropertyDescriptorFactories =
+            new Dictionary<(Type, string), Func<Engine, object, PropertyDescriptor>>();
+
         internal JintCallStack CallStack = new JintCallStack();
 
         static Engine()

+ 7 - 2
Jint/Native/Function/BindFunctionInstance.cs

@@ -23,7 +23,7 @@ namespace Jint.Native.Function
                 ExceptionHelper.ThrowTypeError(Engine);
             });
 
-            return f.Call(BoundThis, BoundArgs.Union(arguments).ToArray());
+            return f.Call(BoundThis, CreateArguments(arguments));
         }
 
         public ObjectInstance Construct(JsValue[] arguments)
@@ -33,7 +33,7 @@ namespace Jint.Native.Function
                 ExceptionHelper.ThrowTypeError(Engine);
             });
 
-            return target.Construct(BoundArgs.Union(arguments).ToArray());
+            return target.Construct(CreateArguments(arguments));
         }
 
         public override bool HasInstance(JsValue v)
@@ -45,5 +45,10 @@ namespace Jint.Native.Function
 
             return f.HasInstance(v);
         }
+
+        private JsValue[] CreateArguments(JsValue[] arguments)
+        {
+            return Enumerable.Union(BoundArgs, arguments).ToArray();
+        }
     }
 }

+ 1 - 1
Jint/Native/JsValue.cs

@@ -280,7 +280,7 @@ namespace Jint.Native
                 return new DelegateWrapper(engine, d);
             }
 
-            if (value.GetType().IsEnum())
+            if (value.GetType().IsEnum)
             {
                 return JsNumber.Create((int) value);
             }

+ 5 - 5
Jint/Native/Number/Dtoa/DiyFp.cs

@@ -38,7 +38,7 @@ namespace Jint.Native.Number.Dtoa
 // have the most significant bit of the significand set.
 // Multiplication and Subtraction do not normalize their results.
 // DiyFp are not designed to contain special doubles (NaN and Infinity).
-    internal struct DiyFp
+    internal readonly struct DiyFp
     {
         internal const int KSignificandSize = 64;
         private const ulong KUint64MSB = 0x8000000000000000L;
@@ -49,8 +49,8 @@ namespace Jint.Native.Number.Dtoa
             E = e;
         }
 
-        public long F { get; }
-        public int E { get; }
+        public readonly long F;
+        public readonly int E;
 
         private static bool Uint64Gte(long a, long b)
         {
@@ -61,7 +61,7 @@ namespace Jint.Native.Number.Dtoa
         // Returns a - b.
         // The exponents of both numbers must be the same and this must be bigger
         // than other. The result will not be normalized.
-        internal static DiyFp Minus(DiyFp a, DiyFp b)
+        internal static DiyFp Minus(in DiyFp a, in DiyFp b)
         {
             Debug.Assert(a.E == b.E);
             Debug.Assert(Uint64Gte(a.F, b.F));
@@ -72,7 +72,7 @@ namespace Jint.Native.Number.Dtoa
         // this = this * other.
 
         // returns a * b;
-        internal static DiyFp Times(DiyFp a, DiyFp b)
+        internal static DiyFp Times(in DiyFp a, in DiyFp b)
         {
             DiyFp result = new DiyFp(a.F, a.E);
             // Simply "emulates" a 128 bit multiplication.

+ 4 - 3
Jint/Native/Number/Dtoa/FastDtoa.cs

@@ -349,9 +349,10 @@ namespace Jint.Native.Number.Dtoa
         // represent 'w' we can stop. Everything inside the interval low - high
         // represents w. However we have to pay attention to low, high and w's
         // imprecision.
-        private static bool DigitGen(DiyFp low,
-            DiyFp w,
-            DiyFp high,
+        private static bool DigitGen(
+            in DiyFp low,
+            in DiyFp w,
+            in DiyFp high,
             FastDtoaBuilder buffer,
             int mk)
         {

+ 0 - 65
Jint/ReflectionExtensions.cs

@@ -1,65 +0,0 @@
-#if NETSTANDARD1_3
-using System;
-using System.Linq;
-using System.Reflection;
-
-namespace Jint
-{
-    internal static class ReflectionExtensions
-    {
-        internal static bool IsEnum(this Type type)
-        {
-            return type.GetTypeInfo().IsEnum;
-        }
-
-        internal static bool IsGenericType(this Type type)
-        {
-            return type.GetTypeInfo().IsGenericType;
-        }
-
-        internal static bool IsValueType(this Type type)
-        {
-            return type.GetTypeInfo().IsValueType;
-        }
-
-        internal static bool HasAttribute<T>(this ParameterInfo member) where T : Attribute
-        {
-            return member.GetCustomAttributes<T>().Any();
-        }
-    }
-}
-#else
-using System;
-using System.Reflection;
-
-namespace Jint
-{
-    internal static class ReflectionExtensions
-    {
-        internal static bool IsEnum(this Type type)
-        {
-            return type.IsEnum;
-        }
-
-        internal static bool IsGenericType(this Type type)
-        {
-            return type.IsGenericType;
-        }
-
-        internal static bool IsValueType(this Type type)
-        {
-            return type.IsValueType;
-        }
-
-        internal static bool HasAttribute<T>(this ParameterInfo member) where T : Attribute
-        {
-            return Attribute.IsDefined(member, typeof(T));
-        }
-
-        internal static MethodInfo GetMethodInfo(this Delegate d)
-        {
-            return d.Method;
-        }
-    }
-}
-#endif

+ 1 - 1
Jint/Runtime/Arguments.cs

@@ -23,7 +23,7 @@ namespace Jint.Runtime
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static JsValue At(this JsValue[] args, int index, JsValue undefinedValue)
         {
-            return args.Length > index ? args[index] : undefinedValue;
+            return index < args.Length ? args[index] : undefinedValue;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 7 - 7
Jint/Runtime/CallStack/CallStackElement.cs

@@ -1,11 +1,11 @@
-namespace Jint.Runtime
-{
-    using Esprima.Ast;
-    using Jint.Native;
+using Esprima.Ast;
+using Jint.Native;
 
+namespace Jint.Runtime
+{
     public class CallStackElement
     {
-        private string _shortDescription;
+        private readonly string _shortDescription;
 
         public CallStackElement(CallExpression callExpression, JsValue function, string shortDescription)
         {
@@ -14,9 +14,9 @@
             Function = function;
         }
 
-        public CallExpression CallExpression { get; private set; }
+        public CallExpression CallExpression { get; }
 
-        public JsValue Function { get; private set; }
+        public JsValue Function { get; }
 
         public override string ToString()
         {

+ 4 - 4
Jint/Runtime/CallStack/JintCallStack.cs

@@ -1,15 +1,15 @@
 using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace Jint.Runtime.CallStack
 {
-    using System.Collections.Generic;
-    using System.Linq;
 
     public class JintCallStack : IEnumerable<CallStackElement>
     {
-        private Stack<CallStackElement> _stack = new Stack<CallStackElement>();
+        private readonly Stack<CallStackElement> _stack = new Stack<CallStackElement>();
 
-        private Dictionary<CallStackElement, int> _statistics =
+        private readonly Dictionary<CallStackElement, int> _statistics =
             new Dictionary<CallStackElement, int>(new CallStackElementComparer());
 
         public int Push(CallStackElement item)

+ 10 - 3
Jint/Runtime/Descriptors/Specialized/IndexDescriptor.cs

@@ -20,20 +20,27 @@ namespace Jint.Runtime.Descriptors.Specialized
 
             // get all instance indexers with exactly 1 argument
             var indexers = targetType.GetProperties();
+            var paramTypeArray = new Type[1];
 
             // try to find first indexer having either public getter or setter with matching argument type
             foreach (var indexer in indexers)
             {
-                if (indexer.GetIndexParameters().Length != 1) continue;
+                var indexParameters = indexer.GetIndexParameters();
+                if (indexParameters.Length != 1)
+                {
+                    continue;
+                }
+
                 if (indexer.GetGetMethod() != null || indexer.GetSetMethod() != null)
                 {
-                    var paramType = indexer.GetIndexParameters()[0].ParameterType;
+                    var paramType = indexParameters[0].ParameterType;
 
                     if (_engine.ClrTypeConverter.TryConvert(key, paramType, CultureInfo.InvariantCulture, out _key))
                     {
                         _indexer = indexer;
                         // get contains key method to avoid index exception being thrown in dictionaries
-                        _containsKey = targetType.GetMethod("ContainsKey", new Type[] {paramType});
+                        paramTypeArray[0] = paramType;
+                        _containsKey = targetType.GetMethod("ContainsKey", paramTypeArray);
                         break;
                     }
                 }

+ 1 - 1
Jint/Runtime/Interop/ClrFunctionInstance.cs

@@ -17,8 +17,8 @@ namespace Jint.Runtime.Interop
         {
             _func = func;
             Prototype = engine.Function.PrototypeObject;
-            SetOwnProperty("length", new PropertyDescriptor(length, PropertyFlag.AllForbidden));
             Extensible = true;
+            _length = new PropertyDescriptor(length, PropertyFlag.AllForbidden);
         }
 
         public ClrFunctionInstance(Engine engine, Func<JsValue, JsValue[], JsValue> func)

+ 7 - 7
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -40,7 +40,7 @@ namespace Jint.Runtime.Interop
                 return value;
             }
 
-            if (type.IsEnum())
+            if (type.IsEnum)
             {
                 var integer = System.Convert.ChangeType(value, typeof(int), formatProvider);
                 if (integer == null)
@@ -57,7 +57,7 @@ namespace Jint.Runtime.Interop
             {
                 var function = (Func<JsValue, JsValue[], JsValue>)value;
 
-                if (type.IsGenericType())
+                if (type.IsGenericType)
                 {
                     var genericType = type.GetGenericTypeDefinition();
 
@@ -75,7 +75,7 @@ namespace Jint.Runtime.Interop
                         for (var i = 0; i < @params.Length; i++)
                         {
                             var param = @params[i];
-                            if (param.Type.IsValueType())
+                            if (param.Type.IsValueType)
                             {
                                 var boxing = Expression.Convert(param, typeof(object));
                                 tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), boxing);
@@ -89,7 +89,7 @@ namespace Jint.Runtime.Interop
 
                         var callExpresion = Expression.Block(Expression.Call(
                                                 Expression.Call(Expression.Constant(function.Target),
-                                                    function.GetMethodInfo(),
+                                                    function.Method,
                                                     Expression.Constant(JsValue.Undefined, typeof(JsValue)),
                                                     @vars),
                                                 jsValueToObject), Expression.Empty());
@@ -123,7 +123,7 @@ namespace Jint.Runtime.Interop
                                                     convertChangeType,
                                                     Expression.Call(
                                                             Expression.Call(Expression.Constant(function.Target),
-                                                                    function.GetMethodInfo(),
+                                                                    function.Method,
                                                                     Expression.Constant(JsValue.Undefined, typeof(JsValue)),
                                                                     @vars),
                                                             jsValueToObject),
@@ -163,7 +163,7 @@ namespace Jint.Runtime.Interop
                         var callExpression = Expression.Block(
                                                 Expression.Call(
                                                     Expression.Call(Expression.Constant(function.Target),
-                                                        function.GetMethodInfo(),
+                                                        function.Method,
                                                         Expression.Constant(JsValue.Undefined, typeof(JsValue)),
                                                         @vars),
                                                     typeof(JsValue).GetMethod("ToObject")),
@@ -196,7 +196,7 @@ namespace Jint.Runtime.Interop
                 return result;
             }
 
-            if (type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
             {
                 type = Nullable.GetUnderlyingType(type);
             }

+ 18 - 8
Jint/Runtime/Interop/DelegateWrapper.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Globalization;
-using System.Linq;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native.Function;
@@ -14,20 +13,31 @@ namespace Jint.Runtime.Interop
     public sealed class DelegateWrapper : FunctionInstance
     {
         private readonly Delegate _d;
+        private readonly bool _delegateContainsParamsArgument;
 
         public DelegateWrapper(Engine engine, Delegate d) : base(engine, null, null, false)
         {
             _d = d;
             Prototype = engine.Function.PrototypeObject;
+
+            var parameterInfos = _d.Method.GetParameters();
+
+            _delegateContainsParamsArgument = false;
+            foreach (var p in parameterInfos)
+            {
+                if (Attribute.IsDefined(p, typeof(ParamArrayAttribute)))
+                {
+                    _delegateContainsParamsArgument = true;
+                    break;
+                }
+            }
         }
 
         public override JsValue Call(JsValue thisObject, JsValue[] jsArguments)
         {
-            var parameterInfos = _d.GetMethodInfo().GetParameters();
-
-            bool delegateContainsParamsArgument = parameterInfos.Any(p => p.HasAttribute<ParamArrayAttribute>());
+            var parameterInfos = _d.Method.GetParameters();
             int delegateArgumentsCount = parameterInfos.Length;
-            int delegateNonParamsArgumentsCount = delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount;
+            int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount;
 
             int jsArgumentsCount = jsArguments.Length;
             int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount);
@@ -55,7 +65,7 @@ namespace Jint.Runtime.Interop
             // assign null to parameters not provided
             for (var i = jsArgumentsWithoutParamsCount; i < delegateNonParamsArgumentsCount; i++)
             {
-                if (parameterInfos[i].ParameterType.IsValueType())
+                if (parameterInfos[i].ParameterType.IsValueType)
                 {
                     parameters[i] = Activator.CreateInstance(parameterInfos[i].ParameterType);
                 }
@@ -66,7 +76,7 @@ namespace Jint.Runtime.Interop
             }
 
             // assign params to array and converts each objet to expected type
-            if(delegateContainsParamsArgument)
+            if(_delegateContainsParamsArgument)
             {
                 int paramsArgumentIndex = delegateArgumentsCount - 1;
                 int paramsCount = Math.Max(0, jsArgumentsCount - delegateNonParamsArgumentsCount);
@@ -106,7 +116,7 @@ namespace Jint.Runtime.Interop
                     ExceptionHelper.ThrowError(_engine, meaningfulException.Message);
                 }
 
-                throw meaningfulException;         
+                throw meaningfulException;
             }
         }
     }

+ 8 - 9
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -28,14 +28,15 @@ namespace Jint.Runtime.Interop
             var arguments = ProcessParamsArrays(jsArguments, methodInfos);
             var converter = Engine.ClrTypeConverter;
 
-            foreach (var method in TypeConverter.FindBestMatch(Engine, methodInfos, arguments))
+            foreach (var method in TypeConverter.FindBestMatch(methodInfos, arguments))
             {
                 var parameters = new object[arguments.Length];
+                var methodParameters = method.GetParameters();
                 var argumentsMatch = true;
 
                 for (var i = 0; i < arguments.Length; i++)
                 {
-                    var parameterType = method.GetParameters()[i].ParameterType;
+                    var parameterType = methodParameters[i].ParameterType;
 
                     if (typeof(JsValue).IsAssignableFrom(parameterType))
                     {
@@ -62,8 +63,7 @@ namespace Jint.Runtime.Interop
                             break;
                         }
 
-                        var lambdaExpression = parameters[i] as LambdaExpression;
-                        if (lambdaExpression != null)
+                        if (parameters[i] is LambdaExpression lambdaExpression)
                         {
                             parameters[i] = lambdaExpression.Compile();
                         }
@@ -78,7 +78,7 @@ namespace Jint.Runtime.Interop
                 // todo: cache method info
                 try
                 {
-                    return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters));
+                    return FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters));
                 }
                 catch (TargetInvocationException exception)
                 {
@@ -103,15 +103,14 @@ namespace Jint.Runtime.Interop
         /// </summary>
         private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodInfo[] methodInfos)
         {
-            for (var i = 0; i < methodInfos.Length; i++)
+            foreach (var methodInfo in methodInfos)
             {
-                var methodInfo = methodInfos[i];
                 var parameters = methodInfo.GetParameters();
 
                 bool hasParamArrayAttribute = false;
-                for (int j = 0; j < parameters.Length; ++j)
+                foreach (var parameter in parameters)
                 {
-                    if (parameters[j].HasAttribute<ParamArrayAttribute>())
+                    if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
                     {
                         hasParamArrayAttribute = true;
                         break;

+ 24 - 22
Jint/Runtime/Interop/NamespaceReference.cs

@@ -49,7 +49,7 @@ namespace Jint.Runtime.Interop
             var genericTypes = new Type[arguments.Length];
             for (int i = 0; i < arguments.Length; i++)
             {
-                var genericTypeReference = arguments.At(i);
+                var genericTypeReference = arguments[i];
                 if (genericTypeReference.IsUndefined()
                     || !genericTypeReference.IsObject() 
                     || genericTypeReference.AsObject().Class != "TypeReference")
@@ -57,7 +57,7 @@ namespace Jint.Runtime.Interop
                     ExceptionHelper.ThrowTypeError(_engine, "Invalid generic type parameter on " + _path + ", if this is not a generic type / method, are you missing a lookup assembly?");
                 }
 
-                genericTypes[i] = arguments.At(i).As<TypeReference>().ReferenceType;
+                genericTypes[i] = ((TypeReference) genericTypeReference).ReferenceType;
             }
 
             var typeReference = GetPath(_path + "`" + arguments.Length.ToString(CultureInfo.InvariantCulture)).As<TypeReference>();
@@ -89,14 +89,14 @@ namespace Jint.Runtime.Interop
 
         public JsValue GetPath(string path)
         {
-            if (Engine.TypeCache.TryGetValue(path, out var type))
+            if (_engine.TypeCache.TryGetValue(path, out var type))
             {
                 if (type == null)
                 {
-                    return new NamespaceReference(Engine, path);
+                    return new NamespaceReference(_engine, path);
                 }
 
-                return TypeReference.CreateTypeReference(Engine, type);
+                return TypeReference.CreateTypeReference(_engine, type);
             }
 
             // in CoreCLR, for example, classes that used to be in
@@ -107,54 +107,55 @@ namespace Jint.Runtime.Interop
             // search in loaded assemblies
             var lookupAssemblies = new[] {Assembly.GetCallingAssembly(), Assembly.GetExecutingAssembly()};
 
-            var lookupAssembliesLength = lookupAssemblies.Length;
-            for (var i = 0; i < lookupAssembliesLength; i++)
+            foreach (var assembly in lookupAssemblies)
             {
-                var assembly = lookupAssemblies[i];
                 type = assembly.GetType(path);
                 if (type != null)
                 {
-                    Engine.TypeCache.Add(path, type);
-                    return TypeReference.CreateTypeReference(Engine, type);
+                    _engine.TypeCache.Add(path, type);
+                    return TypeReference.CreateTypeReference(_engine, type);
                 }
             }
 
             // search in lookup assemblies
-            foreach (var assembly in Engine.Options._LookupAssemblies)
+            var comparedPath = path.Replace("+", ".");
+            foreach (var assembly in _engine.Options._LookupAssemblies)
             {
                 type = assembly.GetType(path);
                 if (type != null)
                 {
-                    Engine.TypeCache.Add(path, type);
-                    return TypeReference.CreateTypeReference(Engine, type);
+                    _engine.TypeCache.Add(path, type);
+                    return TypeReference.CreateTypeReference(_engine, type);
                 }
 
                 var lastPeriodPos = path.LastIndexOf(".", StringComparison.Ordinal);
                 var trimPath = path.Substring(0, lastPeriodPos);
                 type = GetType(assembly, trimPath);
                 if (type != null)
+                {
                     foreach (Type nType in GetAllNestedTypes(type))
                     {
-                        if (nType.FullName.Replace("+", ".").Equals(path.Replace("+", ".")))
+                        if (nType.FullName.Replace("+", ".").Equals(comparedPath))
                         {
-                            Engine.TypeCache.Add(path.Replace("+", "."), nType);
-                            return TypeReference.CreateTypeReference(Engine, nType);
+                            _engine.TypeCache.Add(comparedPath, nType);
+                            return TypeReference.CreateTypeReference(_engine, nType);
                         }
                     }
+                }
             }
 
             // search for type in mscorlib
             type = System.Type.GetType(path);
             if (type != null)
             {
-                Engine.TypeCache.Add(path, type);
-                return TypeReference.CreateTypeReference(Engine, type);
+                _engine.TypeCache.Add(path, type);
+                return TypeReference.CreateTypeReference(_engine, type);
             }
 
             // the new path doesn't represent a known class, thus return a new namespace instance
 
-            Engine.TypeCache.Add(path, null);
-            return new NamespaceReference(Engine, path);
+            _engine.TypeCache.Add(path, null);
+            return new NamespaceReference(_engine, path);
         }
 
         /// <summary>   Gets a type. </summary>
@@ -165,10 +166,11 @@ namespace Jint.Runtime.Interop
         /// <returns>   The type. </returns>
         private static Type GetType(Assembly assembly, string typeName)
         {
+            var compared = typeName.Replace("+", ".");
             Type[] types = assembly.GetTypes();
             foreach (Type t in types)
             {
-                if (t.FullName.Replace("+", ".") == typeName.Replace("+", "."))
+                if (t.FullName.Replace("+", ".") == compared)
                 {
                     return t;
                 }
@@ -177,7 +179,7 @@ namespace Jint.Runtime.Interop
             return null;
         }
 
-        private static IEnumerable<Type> GetAllNestedTypes(Type type)
+        private static Type[] GetAllNestedTypes(Type type)
         {
             var types = new List<Type>();
             AddNestedTypesRecursively(types, type);

+ 115 - 60
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Linq;
+using System.Collections.Generic;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native.Object;
@@ -13,14 +13,14 @@ namespace Jint.Runtime.Interop
 	/// </summary>
 	public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper
     {
-        public Object Target { get; set; }
-
-        public ObjectWrapper(Engine engine, Object obj)
+        public ObjectWrapper(Engine engine, object obj)
             : base(engine)
         {
             Target = obj;
         }
 
+        public object Target { get; }
+
         public override void Put(string propertyName, JsValue value, bool throwOnError)
         {
             if (!CanPut(propertyName))
@@ -41,103 +41,158 @@ namespace Jint.Runtime.Interop
                 {
                     ExceptionHelper.ThrowTypeError(_engine, "Unknown member: " + propertyName);
                 }
-                else
-                {
-                    return;
-                }
             }
-
-            ownDesc.Value = value;
+            else
+            {
+                ownDesc.Value = value;
+            }
         }
 
         public override PropertyDescriptor GetOwnProperty(string propertyName)
         {
             if (TryGetProperty(propertyName, out var x))
+            {
                 return x;
+            }
 
             var type = Target.GetType();
+            var key = (type, propertyName);
+
+            if (!_engine.ClrPropertyDescriptorFactories.TryGetValue(key, out var factory))
+            {
+                factory = ResolveProperty(type, propertyName);
+                _engine.ClrPropertyDescriptorFactories[key] = factory;
+            }
 
+            var descriptor = factory(_engine, Target);
+            AddProperty(propertyName, descriptor);
+            return descriptor;
+        }
+
+        private static Func<Engine, object, PropertyDescriptor> ResolveProperty(Type type, string propertyName)
+        {
             // look for a property
-            var property = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public)
-                .Where(p => EqualsIgnoreCasing(p.Name, propertyName))
-                .FirstOrDefault();
+            PropertyInfo property = null;
+            foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            {
+                if (EqualsIgnoreCasing(p.Name, propertyName))
+                {
+                    property = p;
+                    break;
+                }
+            }
+
             if (property != null)
             {
-                var descriptor = new PropertyInfoDescriptor(Engine, property, Target);
-                AddProperty(propertyName, descriptor);
-                return descriptor;
+                return (engine, target) => new PropertyInfoDescriptor(engine, property, target);
             }
 
             // look for a field
-            var field = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public)
-                .Where(f => EqualsIgnoreCasing(f.Name, propertyName))
-                .FirstOrDefault();
+            FieldInfo field = null;
+            foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            {
+                if (EqualsIgnoreCasing(f.Name, propertyName))
+                {
+                    field = f;
+                    break;
+                }
+            }
+
             if (field != null)
             {
-                var descriptor = new FieldInfoDescriptor(Engine, field, Target);
-                AddProperty(propertyName, descriptor);
-                return descriptor;
+                return (engine, target) => new FieldInfoDescriptor(engine, field, target);
             }
 
             // if no properties were found then look for a method
-            var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public)
-                .Where(m => EqualsIgnoreCasing(m.Name, propertyName))
-                .ToArray();
+            List<MethodInfo> methods = null;
+            foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            {
+                if (EqualsIgnoreCasing(m.Name, propertyName))
+                {
+                    methods = methods ?? new List<MethodInfo>();
+                    methods.Add(m);
+                }
+            }
 
-            if (methods.Any())
+            if (methods?.Count > 0)
             {
-                var descriptor = new PropertyDescriptor(new MethodInfoFunctionInstance(Engine, methods), PropertyFlag.OnlyEnumerable);
-                AddProperty(propertyName, descriptor);
-                return descriptor;
+                return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, methods.ToArray()), PropertyFlag.OnlyEnumerable);
             }
 
             // if no methods are found check if target implemented indexing
-            if (type.GetProperties().Where(p => p.GetIndexParameters().Length != 0).FirstOrDefault() != null)
+            PropertyInfo first = null;
+            foreach (var p in type.GetProperties())
             {
-                return new IndexDescriptor(Engine, propertyName, Target);
+                if (p.GetIndexParameters().Length != 0)
+                {
+                    first = p;
+                    break;
+                }
             }
 
-            var interfaces = type.GetInterfaces();
+            if (first != null)
+            {
+                return (engine, target) => new IndexDescriptor(engine, propertyName, target);
+            }
 
             // try to find a single explicit property implementation
-            var explicitProperties = (from iface in interfaces
-                                      from iprop in iface.GetProperties()
-                                      where EqualsIgnoreCasing(iprop.Name, propertyName)
-                                      select iprop).ToArray();
+            List<PropertyInfo> list = null;
+            foreach (Type iface in type.GetInterfaces())
+            {
+                foreach (var iprop in iface.GetProperties())
+                {
+                    if (EqualsIgnoreCasing(iprop.Name, propertyName))
+                    {
+                        list = list ?? new List<PropertyInfo>();
+                        list.Add(iprop);
+                    }
+                }
+            }
 
-            if (explicitProperties.Length == 1)
+            if (list?.Count == 1)
             {
-                var descriptor = new PropertyInfoDescriptor(Engine, explicitProperties[0], Target);
-                AddProperty(propertyName, descriptor);
-                return descriptor;
+                return (engine, target) => new PropertyInfoDescriptor(engine, list[0], target);
             }
 
             // try to find explicit method implementations
-            var explicitMethods = (from iface in interfaces
-                                   from imethod in iface.GetMethods()
-                                   where EqualsIgnoreCasing(imethod.Name, propertyName)
-                                   select imethod).ToArray();
+            List<MethodInfo> explicitMethods = null;
+            foreach (Type iface in type.GetInterfaces())
+            {
+                foreach (var imethod in iface.GetMethods())
+                {
+                    if (EqualsIgnoreCasing(imethod.Name, propertyName))
+                    {
+                        explicitMethods = explicitMethods ?? new List<MethodInfo>();
+                        explicitMethods.Add(imethod);
+                    }
+                }
+            }
 
-            if (explicitMethods.Length > 0)
+            if (explicitMethods?.Count > 0)
             {
-                var descriptor = new PropertyDescriptor(new MethodInfoFunctionInstance(Engine, explicitMethods), PropertyFlag.OnlyEnumerable);
-                AddProperty(propertyName, descriptor);
-                return descriptor;
+                return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, explicitMethods.ToArray()), PropertyFlag.OnlyEnumerable);
             }
 
             // try to find explicit indexer implementations
-            var explicitIndexers =
-                (from iface in interfaces
-                 from iprop in iface.GetProperties()
-                 where iprop.GetIndexParameters().Length != 0
-                 select iprop).ToArray();
+            List<PropertyInfo> explicitIndexers = null;
+            foreach (Type iface in type.GetInterfaces())
+            {
+                foreach (var iprop in iface.GetProperties())
+                {
+                    if (iprop.GetIndexParameters().Length != 0)
+                    {
+                        explicitIndexers = explicitIndexers ?? new List<PropertyInfo>();
+                        explicitIndexers.Add(iprop);
+                    }
+                }
+            }
 
-            if (explicitIndexers.Length == 1)
+            if (explicitIndexers?.Count == 1)
             {
-                return new IndexDescriptor(Engine, explicitIndexers[0].DeclaringType, propertyName, Target);
+                return (engine, target) => new IndexDescriptor(engine, explicitIndexers[0].DeclaringType, propertyName, target);
             }
 
-            return PropertyDescriptor.Undefined;
+            return (engine, target) => PropertyDescriptor.Undefined;
         }
 
         private static bool EqualsIgnoreCasing(string s1, string s2)
@@ -145,13 +200,13 @@ namespace Jint.Runtime.Interop
             bool equals = false;
             if (s1.Length == s2.Length)
             {
-                if (s1.Length > 0 && s2.Length > 0)
+                if (s1.Length > 0)
                 {
-                    equals = (s1.ToLower()[0] == s2.ToLower()[0]);
+                    equals = char.ToLowerInvariant(s1[0]) == char.ToLowerInvariant(s2[0]);
                 }
-                if (s1.Length > 1 && s2.Length > 1)
+                if (equals && s1.Length > 1)
                 {
-                    equals = equals && (s1.Substring(1) == s2.Substring(1));
+                    equals = s1.Substring(1) == s2.Substring(1);
                 }
             }
             return equals;

+ 20 - 17
Jint/Runtime/Interop/TypeReference.cs

@@ -1,6 +1,6 @@
 using System;
+using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native.Function;
@@ -27,11 +27,10 @@ namespace Jint.Runtime.Interop
 
             // The value of the [[Prototype]] internal property of the TypeReference constructor is the Function prototype object
             obj.Prototype = engine.Function.PrototypeObject;
-
-            obj.SetOwnProperty("length", new PropertyDescriptor(0, PropertyFlag.AllForbidden));
+            obj._length = new PropertyDescriptor(0, PropertyFlag.AllForbidden);
 
             // The initial value of Boolean.prototype is the Boolean prototype object
-            obj.SetOwnProperty("prototype", new PropertyDescriptor(engine.Object.PrototypeObject, PropertyFlag.AllForbidden));
+            obj._prototype = new PropertyDescriptor(engine.Object.PrototypeObject, PropertyFlag.AllForbidden);
 
             return obj;
         }
@@ -44,7 +43,7 @@ namespace Jint.Runtime.Interop
 
         public ObjectInstance Construct(JsValue[] arguments)
         {
-            if (arguments.Length == 0 && ReferenceType.IsValueType())
+            if (arguments.Length == 0 && ReferenceType.IsValueType)
             {
                 var instance = Activator.CreateInstance(ReferenceType);
                 var result = TypeConverter.ToObject(Engine, JsValue.FromObject(Engine, instance));
@@ -54,16 +53,15 @@ namespace Jint.Runtime.Interop
 
             var constructors = ReferenceType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
 
-            var methods = TypeConverter.FindBestMatch(Engine, constructors, arguments).ToList();
-
-            foreach (var method in methods)
+            foreach (var method in TypeConverter.FindBestMatch(constructors, arguments))
             {
                 var parameters = new object[arguments.Length];
+                var methodParameters = method.GetParameters();
                 try
                 {
                     for (var i = 0; i < arguments.Length; i++)
                     {
-                        var parameterType = method.GetParameters()[i].ParameterType;
+                        var parameterType = methodParameters[i].ParameterType;
 
                         if (typeof(JsValue).IsAssignableFrom(parameterType))
                         {
@@ -79,7 +77,7 @@ namespace Jint.Runtime.Interop
                     }
 
                     var constructor = (ConstructorInfo)method;
-                    var instance = constructor.Invoke(parameters.ToArray());
+                    var instance = constructor.Invoke(parameters);
                     var result = TypeConverter.ToObject(Engine, JsValue.FromObject(Engine, instance));
 
                     // todo: cache method info
@@ -161,7 +159,7 @@ namespace Jint.Runtime.Interop
         {
             // todo: cache members locally
 
-            if (ReferenceType.IsEnum())
+            if (ReferenceType.IsEnum)
             {
                 Array enumValues = Enum.GetValues(ReferenceType);
                 Array enumNames = Enum.GetNames(ReferenceType);
@@ -188,17 +186,22 @@ namespace Jint.Runtime.Interop
                 return new FieldInfoDescriptor(Engine, fieldInfo, Type);
             }
 
-            var methodInfo = ReferenceType
-                .GetMethods(BindingFlags.Public | BindingFlags.Static)
-                .Where(mi => mi.Name == propertyName)
-                .ToArray();
+            List<MethodInfo> methodInfo = null;
+            foreach (var mi in ReferenceType.GetMethods(BindingFlags.Public | BindingFlags.Static))
+            {
+                if (mi.Name == propertyName)
+                {
+                    methodInfo = methodInfo ?? new List<MethodInfo>();
+                    methodInfo.Add(mi);
+                }
+            }
 
-            if (methodInfo.Length == 0)
+            if (methodInfo?.Count == 0)
             {
                 return PropertyDescriptor.Undefined;
             }
 
-            return new PropertyDescriptor(new MethodInfoFunctionInstance(Engine, methodInfo), PropertyFlag.AllForbidden);
+            return new PropertyDescriptor(new MethodInfoFunctionInstance(Engine, methodInfo.ToArray()), PropertyFlag.AllForbidden);
         }
 
         public object Target => ReferenceType;

+ 15 - 12
Jint/Runtime/TypeConverter.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using Esprima.Ast;
@@ -395,26 +394,30 @@ namespace Jint.Runtime
             }
         }
 
-        public static IEnumerable<MethodBase> FindBestMatch(Engine engine, MethodBase[] methods, JsValue[] arguments)
+        public static IEnumerable<MethodBase> FindBestMatch<T>(T[] methods, JsValue[] arguments) where T : MethodBase
         {
-            methods = methods
-                .Where(m => m.GetParameters().Length == arguments.Length)
-                .ToArray();
+            var matchingByParameterCount = new List<T>();
+            foreach (var m in methods)
+            {
+                if (m.GetParameters().Length == arguments.Length)
+                {
+                    matchingByParameterCount.Add(m);
+                }
+            }
 
-            if (methods.Length == 1 && !methods[0].GetParameters().Any())
+            if (matchingByParameterCount.Count == 1 && arguments.Length == 0)
             {
-                yield return methods[0];
+                yield return matchingByParameterCount[0];
                 yield break;
             }
 
-            var objectArguments = arguments.Select(x => x.ToObject()).ToArray();
-            foreach (var method in methods)
+            foreach (var method in matchingByParameterCount)
             {
                 var perfectMatch = true;
                 var parameters = method.GetParameters();
                 for (var i = 0; i < arguments.Length; i++)
                 {
-                    var arg = objectArguments[i];
+                    var arg = arguments[i].ToObject();
                     var paramType = parameters[i].ParameterType;
 
                     if (arg == null)
@@ -439,9 +442,9 @@ namespace Jint.Runtime
                 }
             }
 
-            foreach (var method in methods)
+            for (var i = 0; i < matchingByParameterCount.Count; i++)
             {
-                yield return method;
+                yield return matchingByParameterCount[i];
             }
         }