Переглянути джерело

Cache common JsValue allocations (#456)

Marko Lahma 7 роки тому
батько
коміт
aaf42cc900

+ 21 - 16
Jint/Engine.cs

@@ -45,22 +45,22 @@ namespace Jint
 
         internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new Dictionary<Type, Func<Engine, object, JsValue>>()
         {
-            { typeof(bool), (Engine engine, object v) => new JsValue((bool)v) },
-            { typeof(byte), (Engine engine, object v) => new JsValue((byte)v) },
-            { typeof(char), (Engine engine, object v) => new JsValue((char)v) },
+            { typeof(bool), (Engine engine, object v) => (bool) v ? JsValue.True : JsValue.False },
+            { typeof(byte), (Engine engine, object v) => JsValue.FromInt((byte)v) },
+            { typeof(char), (Engine engine, object v) => JsValue.FromChar((char)v) },
             { typeof(DateTime), (Engine engine, object v) => engine.Date.Construct((DateTime)v) },
             { typeof(DateTimeOffset), (Engine engine, object v) => engine.Date.Construct((DateTimeOffset)v) },
-            { typeof(decimal), (Engine engine, object v) => new JsValue((double)(decimal)v) },
-            { typeof(double), (Engine engine, object v) => new JsValue((double)v) },
-            { typeof(Int16), (Engine engine, object v) => new JsValue((Int16)v) },
-            { typeof(Int32), (Engine engine, object v) => new JsValue((Int32)v) },
-            { typeof(Int64), (Engine engine, object v) => new JsValue((Int64)v) },
-            { typeof(SByte), (Engine engine, object v) => new JsValue((SByte)v) },
-            { typeof(Single), (Engine engine, object v) => new JsValue((Single)v) },
-            { typeof(string), (Engine engine, object v) => new JsValue((string)v) },
-            { typeof(UInt16), (Engine engine, object v) => new JsValue((UInt16)v) },
-            { typeof(UInt32), (Engine engine, object v) => new JsValue((UInt32)v) },
-            { typeof(UInt64), (Engine engine, object v) => new JsValue((UInt64)v) },
+            { typeof(decimal), (Engine engine, object v) => (JsValue) (double)(decimal)v },
+            { typeof(double), (Engine engine, object v) => (JsValue)(double)v },
+            { typeof(Int16), (Engine engine, object v) => JsValue.FromInt((Int16)v) },
+            { typeof(Int32), (Engine engine, object v) => JsValue.FromInt((Int32)v) },
+            { typeof(Int64), (Engine engine, object v) => (JsValue)(Int64)v },
+            { typeof(SByte), (Engine engine, object v) => JsValue.FromInt((SByte)v) },
+            { typeof(Single), (Engine engine, object v) => (JsValue)(Single)v },
+            { typeof(string), (Engine engine, object v) => (JsValue) (string)v },
+            { typeof(UInt16), (Engine engine, object v) => JsValue.FromInt((UInt16)v) },
+            { typeof(UInt32), (Engine engine, object v) => JsValue.FromInt((UInt32)v) },
+            { typeof(UInt64), (Engine engine, object v) => JsValue.FromInt((UInt64)v) },
             { typeof(JsValue), (Engine engine, object v) => (JsValue)v },
             { typeof(System.Text.RegularExpressions.Regex), (Engine engine, object v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "") }
         };
@@ -257,9 +257,14 @@ namespace Jint
             return SetValue(name, new JsValue(value));
         }
 
+        public Engine SetValue(string name, int value)
+        {
+            return SetValue(name, JsValue.FromInt(value));
+        }
+
         public Engine SetValue(string name, bool value)
         {
-            return SetValue(name, new JsValue(value));
+            return SetValue(name, value ? JsValue.True : JsValue.False);
         }
 
         public Engine SetValue(string name, JsValue value)
@@ -539,7 +544,7 @@ namespace Jint
                 {
                     return baseValue;
                 }
-                
+
                 if (reference.HasPrimitiveBase() == false)
                 {
                     var o = TypeConverter.ToObject(this, baseValue);

+ 1 - 1
Jint/Native/Argument/ArgumentsInstance.cs

@@ -163,7 +163,7 @@ namespace Jint.Native.Argument
             if (desc.IsAccessorDescriptor())
             {
                 var setter = desc.Set.TryCast<ICallable>();
-                setter.Call(new JsValue(this), new[] { value });
+                setter.Call(JsValue, new[] { value });
             }
             else
             {

+ 2 - 2
Jint/Native/Array/ArrayInstance.cs

@@ -52,7 +52,7 @@ namespace Jint.Native.Array
             if (desc.IsAccessorDescriptor())
             {
                 var setter = desc.Set.TryCast<ICallable>();
-                setter.Call(new JsValue(this), new[] { value });
+                setter.Call(JsValue, new[] { value });
             }
             else
             {
@@ -128,7 +128,7 @@ namespace Jint.Native.Array
                             var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
                             if (!deleteSucceeded)
                             {
-                                newLenDesc.Value = new JsValue(keyIndex + 1);
+                                newLenDesc.Value = JsValue.FromInt(keyIndex + 1);
                                 if (!newWritable)
                                 {
                                     newLenDesc.Writable = false;

+ 1 - 1
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -30,7 +30,7 @@ namespace Jint.Native.Function
             Extensible = true;
             Prototype = engine.Function.PrototypeObject;
 
-            DefineOwnProperty("length", new PropertyDescriptor(new JsValue(FormalParameters.Length), false, false, false ), false);
+            DefineOwnProperty("length", new PropertyDescriptor(JsValue.FromInt(FormalParameters.Length), false, false, false ), false);
 
             var proto = engine.Object.Construct(Arguments.Empty);
             proto.DefineOwnProperty("constructor", new PropertyDescriptor(this, true, false, true), false);

+ 113 - 19
Jint/Native/JsValue.cs

@@ -20,10 +20,39 @@ namespace Jint.Native
     [DebuggerTypeProxy(typeof(JsValueDebugView))]
     public class JsValue : IEquatable<JsValue>
     {
-        public readonly static JsValue Undefined = new JsValue(Types.Undefined);
-        public readonly static JsValue Null = new JsValue(Types.Null);
-        public readonly static JsValue False = new JsValue(false);
-        public readonly static JsValue True = new JsValue(true);
+        // we can cache most common values, doubles are used in indexing too at times so we also cache
+        // integer values converted to doubles
+        private static readonly Dictionary<double, JsValue> _doubleToJsValue = new Dictionary<double, JsValue>();
+        private static readonly JsValue[] _intToJsValue = new JsValue[1024];
+        private static readonly JsValue[] _charToJsValue = new JsValue[256];
+
+        private static readonly JsValue _function = new JsValue("function");
+
+        public static readonly JsValue Undefined = new JsValue(Types.Undefined);
+        public static readonly JsValue Null = new JsValue(Types.Null);
+        public static readonly JsValue False = new JsValue(false);
+        public static readonly JsValue True = new JsValue(true);
+
+        private readonly double _double;
+        private readonly object _object;
+        protected Types _type;
+
+        static JsValue()
+        {
+            for (int i = 0; i < _intToJsValue.Length; i++)
+            {
+                _intToJsValue[i] = new JsValue(i);
+                if (i != 0)
+                {
+                    // zero can be problematic
+                    _doubleToJsValue[i] = new JsValue((double) i);
+                }
+            }
+            for (int i = 0; i < _charToJsValue.Length; i++)
+            {
+                _charToJsValue[i] = new JsValue((char) i);
+            }
+        }
 
         public JsValue(bool value)
         {
@@ -40,6 +69,29 @@ namespace Jint.Native
             _double = value;
         }
 
+        public JsValue(int value)
+        {
+            _object = null;
+            _type = Types.Number;
+
+            _double = value;
+        }
+
+        public JsValue(uint value)
+        {
+            _object = null;
+            _type = Types.Number;
+
+            _double = value;
+        }
+
+        public JsValue(char value)
+        {
+            _double = double.NaN;
+            _object = value;
+            _type = Types.String;
+        }
+
         public JsValue(string value)
         {
             _double = double.NaN;
@@ -70,12 +122,6 @@ namespace Jint.Native
             _type = type;
         }
 
-        protected double _double;
-
-        protected object _object;
-
-        protected Types _type;
-
         [Pure]
         public bool IsPrimitive()
         {
@@ -335,9 +381,42 @@ namespace Jint.Native
             }
         }
 
-        public Types Type
+        public Types Type => _type;
+
+        internal static JsValue FromInt(int value)
+        {
+            if (value >= 0 && value < _intToJsValue.Length)
+            {
+                return _intToJsValue[value];
+            }
+            return new JsValue(value);
+        }
+
+        internal static JsValue FromInt(uint value)
+        {
+            if (value >= 0 && value < _intToJsValue.Length)
+            {
+                return _intToJsValue[value];
+            }
+            return new JsValue(value);
+        }
+
+        internal static JsValue FromInt(ulong value)
         {
-            get { return _type; }
+            if (value >= 0 && value < (ulong) _intToJsValue.Length)
+            {
+                return _intToJsValue[value];
+            }
+            return new JsValue(value);
+        }
+
+        internal static JsValue FromChar(char value)
+        {
+            if (value >= 0 && value < _charToJsValue.Length)
+            {
+                return _charToJsValue[value];
+            }
+            return new JsValue(value);
         }
 
         /// <summary>
@@ -380,16 +459,16 @@ namespace Jint.Native
                 // Learn conversion, racy, worst case we'll try again later
                 Interlocked.CompareExchange(ref Engine.TypeMappers, new Dictionary<Type, Func<Engine, object, JsValue>>(typeMappers)
                 {
-                    [valueType] = (Engine e, object v) => new JsValue((ObjectInstance)v)
+                    [valueType] = (Engine e, object v) => ((ObjectInstance)v).JsValue
                 }, typeMappers);
-                return new JsValue(instance);
+                return instance.JsValue;
             }
 
             var type = value as Type;
             if(type != null)
             {
                 var typeReference = TypeReference.CreateTypeReference(engine, type);
-                return new JsValue(typeReference);
+                return typeReference.JsValue;
             }
 
             var a = value as System.Array;
@@ -424,7 +503,7 @@ namespace Jint.Native
 
             if (value.GetType().IsEnum())
             {
-                return new JsValue((Int32)value);
+                return FromInt((int) value);
             }
 
             // if no known type could be guessed, wrap it as an ObjectInstance
@@ -662,24 +741,39 @@ namespace Jint.Native
             return !a.Equals(b);
         }
 
+        static public implicit operator JsValue(int value)
+        {
+            return FromInt(value);
+        }
+
         static public implicit operator JsValue(double value)
         {
-            return new JsValue(value);
+            if (value < 0 || value >= _doubleToJsValue.Count || !_doubleToJsValue.TryGetValue(value, out var jsValue))
+            {
+                jsValue = new JsValue(value);
+            }
+            return jsValue;
         }
 
         static public implicit operator JsValue(bool value)
         {
-            return new JsValue(value);
+            return value ? True : False;
         }
 
         static public implicit operator JsValue(string value)
         {
+            // some common ones can be cached here
+            if (value == "function")
+            {
+                return _function;
+            }
+
             return new JsValue(value);
         }
 
         static public implicit operator JsValue(ObjectInstance value)
         {
-            return new JsValue(value);
+            return value.JsValue;
         }
 
         internal class JsValueDebugView

+ 1 - 1
Jint/Native/Json/JsonParser.cs

@@ -787,7 +787,7 @@ namespace Jint.Native.Json
                     var v = Lex().Value;
                     return Null.Instance;
                 case Tokens.BooleanLiteral:
-                    return new JsValue((bool)Lex().Value);
+                    return (bool) Lex().Value ? JsValue.True : JsValue.False;
                 case Tokens.String:
                     return new JsValue((string)Lex().Value);
                 case Tokens.Number:

+ 14 - 5
Jint/Native/Object/ObjectInstance.cs

@@ -8,6 +8,7 @@ namespace Jint.Native.Object
 {
     public class ObjectInstance
     {
+        private JsValue _jsValue;
         private Dictionary<string, PropertyDescriptor> _intrinsicProperties;
 
         public ObjectInstance(Engine engine)
@@ -20,6 +21,14 @@ namespace Jint.Native.Object
 
         protected IDictionary<string, PropertyDescriptor> Properties { get; private set; }
 
+        /// <summary>
+        /// Caches the constructed JS.
+        /// </summary>
+        internal JsValue JsValue
+        {
+            get { return _jsValue = _jsValue ?? new JsValue(this); }
+        }
+
         protected bool TryGetIntrinsicValue(JsSymbol symbol, out JsValue value)
         {
             PropertyDescriptor descriptor;
@@ -228,7 +237,7 @@ namespace Jint.Native.Object
             if (desc.IsAccessorDescriptor())
             {
                 var setter = desc.Set.TryCast<ICallable>();
-                setter.Call(new JsValue(this), new [] {value});
+                setter.Call(JsValue, new [] {value});
             }
             else
             {
@@ -356,7 +365,7 @@ namespace Jint.Native.Object
                 var toString = Get("toString").TryCast<ICallable>();
                 if (toString != null)
                 {
-                    var str = toString.Call(new JsValue(this), Arguments.Empty);
+                    var str = toString.Call(JsValue, Arguments.Empty);
                     if (str.IsPrimitive())
                     {
                         return str;
@@ -366,7 +375,7 @@ namespace Jint.Native.Object
                 var valueOf = Get("valueOf").TryCast<ICallable>();
                 if (valueOf != null)
                 {
-                    var val = valueOf.Call(new JsValue(this), Arguments.Empty);
+                    var val = valueOf.Call(JsValue, Arguments.Empty);
                     if (val.IsPrimitive())
                     {
                         return val;
@@ -381,7 +390,7 @@ namespace Jint.Native.Object
                 var valueOf = Get("valueOf").TryCast<ICallable>();
                 if (valueOf != null)
                 {
-                    var val = valueOf.Call(new JsValue(this), Arguments.Empty);
+                    var val = valueOf.Call(JsValue, Arguments.Empty);
                     if (val.IsPrimitive())
                     {
                         return val;
@@ -391,7 +400,7 @@ namespace Jint.Native.Object
                 var toString = Get("toString").TryCast<ICallable>();
                 if (toString != null)
                 {
-                    var str = toString.Call(new JsValue(this), Arguments.Empty);
+                    var str = toString.Call(JsValue, Arguments.Empty);
                     if (str.IsPrimitive())
                     {
                         return str;

+ 1 - 1
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -68,7 +68,7 @@ namespace Jint.Runtime.Environments
         {
             if (_provideThis)
             {
-                return new JsValue(_bindingObject);
+                return _bindingObject.JsValue;
             }
 
             return Undefined.Instance;

+ 10 - 13
Jint/Runtime/ExpressionIntepreter.cs

@@ -5,7 +5,6 @@ using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Number;
-using Jint.Native.Object;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Interop;
@@ -610,19 +609,17 @@ namespace Jint.Runtime
 
         public JsValue EvaluateLiteral(Literal literal)
         {
-            if (literal.Cached)
+            switch (literal.TokenType)
             {
-                switch (literal.TokenType)
-                {
-                    case TokenType.BooleanLiteral:
-                        return new JsValue(literal.BooleanValue);
-                    case TokenType.NullLiteral:
-                        return JsValue.Null;
-                    case TokenType.NumericLiteral:
-                        return new JsValue(literal.NumericValue);
-                    case TokenType.StringLiteral:
-                        return new JsValue(literal.StringValue);
-                }
+                case TokenType.BooleanLiteral:
+                    return literal.BooleanValue ? JsValue.True : JsValue.False;
+                case TokenType.NullLiteral:
+                    return JsValue.Null;
+                case TokenType.NumericLiteral:
+                    // implicit conversion operator goes through caching
+                    return literal.NumericValue;
+                case TokenType.StringLiteral:
+                    return new JsValue(literal.StringValue);
             }
 
             if (literal.RegexValue != null) //(literal.Type == Nodes.RegularExpressionLiteral)

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

@@ -129,7 +129,7 @@ namespace Jint.Runtime.Interop
                 var jsArray = Engine.Array.Construct(Arguments.Empty);
                 Engine.Array.PrototypeObject.Push(jsArray, argsToTransform.ToArray());
 
-                newArgumentsCollection.Add(new JsValue(jsArray));
+                newArgumentsCollection.Add(jsArray.JsValue);
                 return newArgumentsCollection.ToArray();
             }