Browse Source

#451 optimize object expressions and type conversions (#521)

Marko Lahma 7 năm trước cách đây
mục cha
commit
c6881dc92c

+ 11 - 4
Jint/Native/Boolean/BooleanConstructor.cs

@@ -57,10 +57,17 @@ namespace Jint.Native.Boolean
 
         public BooleanInstance Construct(bool value)
         {
-            var instance = new BooleanInstance(Engine);
-            instance.Prototype = PrototypeObject;
-            instance.PrimitiveValue = value;
-            instance.Extensible = true;
+            return Construct(value ? JsBoolean.True : JsBoolean.False);
+        }
+        
+        public BooleanInstance Construct(JsBoolean value)
+        {
+            var instance = new BooleanInstance(Engine)
+            {
+                Prototype = PrototypeObject,
+                PrimitiveValue = value,
+                Extensible = true
+            };
 
             return instance;
         }

+ 2 - 2
Jint/Native/JsBoolean.cs

@@ -5,8 +5,8 @@ namespace Jint.Native
 {
     public sealed class JsBoolean : JsValue, IEquatable<JsBoolean>
     {
-        public static readonly JsValue False = new JsBoolean(false);
-        public static readonly JsValue True = new JsBoolean(true);
+        public static readonly JsBoolean False = new JsBoolean(false);
+        public static readonly JsBoolean True = new JsBoolean(true);
 
         internal static readonly object BoxedTrue = true;
         internal static readonly object BoxedFalse = false;

+ 8 - 5
Jint/Native/JsString.cs

@@ -10,7 +10,7 @@ namespace Jint.Native
         private static readonly JsString[] _charToJsValue;
         private static readonly JsString[] _charToStringJsValue;
 
-        private static readonly JsString Empty = new JsString("");
+        public static readonly JsString Empty = new JsString("");
         private static readonly JsString NullString = new JsString("null");
 
         internal string _value;
@@ -57,6 +57,8 @@ namespace Jint.Native
             return string.IsNullOrEmpty(_value);
         }
 
+        public virtual int Length => _value.Length;
+
         internal static JsString Create(string value)
         {
             if (value.Length == 0)
@@ -178,6 +180,8 @@ namespace Jint.Native
                     || _stringBuilder != null && _stringBuilder.Length == 0;
             }
 
+            public override int Length => _stringBuilder?.Length ?? _value?.Length ?? 0;
+
             public override object ToObject()
             {
                 return _stringBuilder.ToString();
@@ -190,15 +194,14 @@ namespace Jint.Native
                     return _stringBuilder.Equals(cs._stringBuilder);
                 }
 
-                if (other.Type == Types.String)
+                if (other is JsString jsString)
                 {
-                    var otherString = other.AsStringWithoutTypeCheck();
-                    if (otherString.Length != _stringBuilder.Length)
+                    if (jsString._value.Length != Length)
                     {
                         return false;
                     }
 
-                    return ToString() == otherString;
+                    return ToString() == jsString._value;
                 }
 
                 return base.Equals(other);

+ 11 - 4
Jint/Native/Number/NumberConstructor.cs

@@ -63,10 +63,17 @@ namespace Jint.Native.Number
 
         public NumberInstance Construct(double value)
         {
-            var instance = new NumberInstance(Engine);
-            instance.Prototype = PrototypeObject;
-            instance.NumberData = value;
-            instance.Extensible = true;
+            return Construct(JsNumber.Create(value));
+        }
+
+        public NumberInstance Construct(JsNumber value)
+        {
+            var instance = new NumberInstance(Engine)
+            {
+                Prototype = PrototypeObject,
+                NumberData = value,
+                Extensible = true
+            };
 
             return instance;
         }

+ 1 - 1
Jint/Native/Number/NumberInstance.cs

@@ -18,7 +18,7 @@ namespace Jint.Native.Number
 
         JsValue IPrimitiveInstance.PrimitiveValue => NumberData;
 
-        public JsValue NumberData { get; set; }
+        public JsNumber NumberData { get; set; }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool IsNegativeZero(double x)

+ 6 - 4
Jint/Native/Number/NumberPrototype.cs

@@ -21,10 +21,12 @@ namespace Jint.Native.Number
 
         public static NumberPrototype CreatePrototypeObject(Engine engine, NumberConstructor numberConstructor)
         {
-            var obj = new NumberPrototype(engine);
-            obj.Prototype = engine.Object.PrototypeObject;
-            obj.NumberData = 0;
-            obj.Extensible = true;
+            var obj = new NumberPrototype(engine)
+            {
+                Prototype = engine.Object.PrototypeObject,
+                NumberData = JsNumber.Create(0),
+                Extensible = true
+            };
 
             obj.FastAddProperty("constructor", numberConstructor, true, false, true);
 

+ 13 - 1
Jint/Native/Object/ObjectConstructor.cs

@@ -100,6 +100,18 @@ namespace Jint.Native.Object
 
             return obj;
         }
+        
+        internal ObjectInstance Construct(int propertyCount)
+        {
+            var obj = new ObjectInstance(_engine)
+            {
+                Extensible = true,
+                Prototype = Engine.Object.PrototypeObject,
+                _properties =  propertyCount > 0 ? new Dictionary<string, PropertyDescriptor>(propertyCount) : null
+            };
+
+            return obj;
+        }
 
         public JsValue GetPrototypeOf(JsValue thisObject, JsValue[] arguments)
         {
@@ -144,7 +156,7 @@ namespace Jint.Native.Object
             var ownProperties = o.GetOwnProperties().ToList();
             if (o is StringInstance s)
             {
-                var length = s.PrimitiveValue.AsStringWithoutTypeCheck().Length;
+                var length = s.PrimitiveValue.Length;
                 array = Engine.Array.ConstructFast((uint) (ownProperties.Count + length));
                 for (var i = 0; i < length; i++)
                 {

+ 3 - 3
Jint/Native/Object/ObjectInstance.cs

@@ -19,7 +19,7 @@ namespace Jint.Native.Object
     public class ObjectInstance : JsValue, IEquatable<ObjectInstance>
     {
         protected Dictionary<string, PropertyDescriptor> _intrinsicProperties;
-        protected Dictionary<string, PropertyDescriptor> _properties;
+        protected internal Dictionary<string, PropertyDescriptor> _properties;
         
         private readonly string _class;
         protected readonly Engine _engine;
@@ -780,7 +780,7 @@ namespace Jint.Native.Object
                 case "String":
                     if (this is StringInstance stringInstance)
                     {
-                        return stringInstance.PrimitiveValue.AsStringWithoutTypeCheck();
+                        return stringInstance.PrimitiveValue.ToString();
                     }
 
                     break;
@@ -814,7 +814,7 @@ namespace Jint.Native.Object
                 case "Number":
                     if (this is NumberInstance numberInstance)
                     {
-                        return ((JsNumber) numberInstance.NumberData)._value;
+                        return numberInstance.NumberData._value;
                     }
 
                     break;

+ 11 - 5
Jint/Native/String/StringConstructor.cs

@@ -70,12 +70,18 @@ namespace Jint.Native.String
 
         public StringInstance Construct(string value)
         {
-            var instance = new StringInstance(Engine);
-            instance.Prototype = PrototypeObject;
-            instance.PrimitiveValue = value;
-            instance.Extensible = true;
+            return Construct(JsString.Create(value));
+        }
 
-            instance.SetOwnProperty("length", new PropertyDescriptor(value.Length, PropertyFlag.AllForbidden));
+        public StringInstance Construct(JsString value)
+        {
+            var instance = new StringInstance(Engine)
+            {
+                Prototype = PrototypeObject,
+                PrimitiveValue = value,
+                Extensible = true,
+                _length = new PropertyDescriptor(value.Length, PropertyFlag.AllForbidden)
+            };
 
             return instance;
         }

+ 2 - 2
Jint/Native/String/StringInstance.cs

@@ -10,7 +10,7 @@ namespace Jint.Native.String
         private const string PropertyNameLength = "length";
         private const int PropertyNameLengthLength = 6;
 
-        private PropertyDescriptor _length;
+        internal PropertyDescriptor _length;
 
         public StringInstance(Engine engine)
             : base(engine, objectClass: "String")
@@ -21,7 +21,7 @@ namespace Jint.Native.String
 
         JsValue IPrimitiveInstance.PrimitiveValue => PrimitiveValue;
 
-        public JsValue PrimitiveValue { get; set; }
+        public JsString PrimitiveValue { get; set; }
 
         private static bool IsInt(double d)
         {

+ 1 - 1
Jint/Native/String/StringPrototype.cs

@@ -25,7 +25,7 @@ namespace Jint.Native.String
         {
             var obj = new StringPrototype(engine);
             obj.Prototype = engine.Object.PrototypeObject;
-            obj.PrimitiveValue = "";
+            obj.PrimitiveValue = JsString.Empty;
             obj.Extensible = true;
             obj.SetOwnProperty("length", new PropertyDescriptor(0, PropertyFlag.AllForbidden));
             obj.SetOwnProperty("constructor", new PropertyDescriptor(stringConstructor, PropertyFlag.NonEnumerable));

+ 11 - 4
Jint/Native/Symbol/SymbolConstructor.cs

@@ -115,10 +115,17 @@ namespace Jint.Native.Symbol
 
         public SymbolInstance Construct(string description)
         {
-            var instance = new SymbolInstance(Engine);
-            instance.Prototype = PrototypeObject;
-            instance.SymbolData = new JsSymbol(description);
-            instance.Extensible = true;
+            return Construct(new JsSymbol(description));
+        }
+
+        public SymbolInstance Construct(JsSymbol symbol)
+        {
+            var instance = new SymbolInstance(Engine)
+            {
+                Prototype = PrototypeObject,
+                SymbolData = symbol, 
+                Extensible = true
+            };
 
             return instance;
         }

+ 86 - 103
Jint/Runtime/ExpressionIntepreter.cs

@@ -275,11 +275,11 @@ namespace Jint.Runtime
                     break;
 
                 case BinaryOperator.Equal:
-                    value = Equal(left, right);
+                    value = Equal(left, right) ? JsBoolean.True : JsBoolean.False;
                     break;
 
                 case BinaryOperator.NotEqual:
-                    value = !Equal(left, right);
+                    value = Equal(left, right) ? JsBoolean.False : JsBoolean.True;
                     break;
 
                 case BinaryOperator.Greater:
@@ -323,10 +323,10 @@ namespace Jint.Runtime
                     break;
 
                 case BinaryOperator.StrictlyEqual:
-                    return StrictlyEqual(left, right);
+                    return StrictlyEqual(left, right) ? JsBoolean.True : JsBoolean.False;
 
                 case BinaryOperator.StricltyNotEqual:
-                    return !StrictlyEqual(left, right);
+                    return StrictlyEqual(left, right)? JsBoolean.False : JsBoolean.True;
 
                 case BinaryOperator.BitwiseAnd:
                     return TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right);
@@ -402,50 +402,47 @@ namespace Jint.Runtime
 
         private static bool Equal(JsValue x, JsValue y)
         {
-            var typex = x.Type;
-            var typey = y.Type;
-
-            if (typex == typey)
+            if (x._type == y._type)
             {
 				return StrictlyEqual(x, y);
             }
 
-            if (x.IsNull() && y.IsUndefined())
+            if (x._type == Types.Null && y._type == Types.Undefined)
             {
                 return true;
             }
 
-            if (x.IsUndefined() && y.IsNull())
+            if (x._type == Types.Undefined && y._type == Types.Null)
             {
                 return true;
             }
 
-            if (typex == Types.Number && typey == Types.String)
+            if (x._type == Types.Number && y._type == Types.String)
             {
                 return Equal(x, TypeConverter.ToNumber(y));
             }
 
-            if (typex == Types.String && typey == Types.Number)
+            if (x._type == Types.String && y._type == Types.Number)
             {
                 return Equal(TypeConverter.ToNumber(x), y);
             }
 
-            if (typex == Types.Boolean)
+            if (x._type == Types.Boolean)
             {
                 return Equal(TypeConverter.ToNumber(x), y);
             }
 
-            if (typey == Types.Boolean)
+            if (y._type == Types.Boolean)
             {
                 return Equal(x, TypeConverter.ToNumber(y));
             }
 
-            if (typey == Types.Object && (typex == Types.String || typex == Types.Number))
+            if (y._type == Types.Object && (x._type == Types.String || x._type == Types.Number))
             {
                 return Equal(x, TypeConverter.ToPrimitive(y));
             }
 
-            if (typex == Types.Object && (typey == Types.String || typey == Types.Number))
+            if (x._type == Types.Object && (y._type == Types.String || y._type == Types.Number))
             {
                 return Equal(TypeConverter.ToPrimitive(x), y);
             }
@@ -455,37 +452,40 @@ namespace Jint.Runtime
 
         public static bool StrictlyEqual(JsValue x, JsValue y)
         {
-            var typea = x.Type;
-            var typeb = y.Type;
-
-            if (typea != typeb)
+            if (x._type != y._type)
             {
                 return false;
             }
 
-            switch (typea)
+            if (x._type == Types.Boolean || x._type == Types.String)
             {
-                case Types.Undefined:
-                case Types.Null:
-                    return true;
-                case Types.Number:
-                    var nx = ((JsNumber) x)._value;
-                    var ny = ((JsNumber) y)._value;
-                    return !double.IsNaN(nx) && !double.IsNaN(ny) && nx == ny;
-                case Types.String:
-                    return x.AsStringWithoutTypeCheck() == y.AsStringWithoutTypeCheck();
-                case Types.Boolean:
-                    return ((JsBoolean) x)._value == ((JsBoolean) y)._value;
-                case Types.Object when x.AsObject() is IObjectWrapper xw:
-                    var yw = y.AsObject() as IObjectWrapper;
-                    if (yw == null)
-                        return false;
-                    return Equals(xw.Target, yw.Target);
-                case Types.None:
-                    return true;
-                default:
-                    return x == y;
+                return x.Equals(y);
+            }
+
+                        
+            if (x._type >= Types.None && x._type <= Types.Null)
+            {
+                return true;
             }
+
+            if (x is JsNumber jsNumber)
+            {
+                var nx = jsNumber._value;
+                var ny = ((JsNumber) y)._value;
+                return !double.IsNaN(nx) && !double.IsNaN(ny) && nx == ny;
+            }
+
+            if (x is IObjectWrapper xw)
+            {
+                if (!(y is IObjectWrapper yw))
+                {
+                    return false;
+                }
+
+                return Equals(xw.Target, yw.Target);
+            }
+
+            return x == y;
         }
 
         public static bool SameValue(JsValue x, JsValue y)
@@ -632,74 +632,54 @@ namespace Jint.Runtime
         public JsValue EvaluateObjectExpression(ObjectExpression objectExpression)
         {
             // http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.5
-
-            var obj = _engine.Object.Construct(Arguments.Empty);
             var propertiesCount = objectExpression.Properties.Count;
+            var obj = _engine.Object.Construct(propertiesCount);
             for (var i = 0; i < propertiesCount; i++)
             {
                 var property = objectExpression.Properties[i];
                 var propName = property.Key.GetKey();
-                var previous = obj.GetOwnProperty(propName);
+                if (!obj._properties.TryGetValue(propName, out var previous))
+                {
+                    previous = PropertyDescriptor.Undefined;
+                }
+                
                 PropertyDescriptor propDesc;
 
-                const PropertyFlag enumerableConfigurable = PropertyFlag.Enumerable | PropertyFlag.Configurable;
-                
-                switch (property.Kind)
+                if (property.Kind == PropertyKind.Init || property.Kind == PropertyKind.Data)
                 {
-                    case PropertyKind.Init:
-                    case PropertyKind.Data:
-                        var exprValue = _engine.EvaluateExpression(property.Value);
-                        var propValue = _engine.GetValue(exprValue, true);
-                        propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable);
-                        break;
-
-                    case PropertyKind.Get:
-                        var getter = property.Value as IFunction;
-
-                        if (getter == null)
-                        {
-                            ExceptionHelper.ThrowSyntaxError(_engine);
-                        }
-
-                        ScriptFunctionInstance get;
-                        using (new StrictModeScope(getter.Strict))
-                        {
-                            get = new ScriptFunctionInstance(
-                                _engine,
-                                getter,
-                                _engine.ExecutionContext.LexicalEnvironment,
-                                StrictModeScope.IsStrictModeCode
-                            );
-                        }
-
-                        propDesc = new GetSetPropertyDescriptor(get: get, set: null, enumerableConfigurable);
-                        break;
-
-                    case PropertyKind.Set:
-                        var setter = property.Value as IFunction;
-
-                        if (setter == null)
-                        {
-                            ExceptionHelper.ThrowSyntaxError(_engine);
-                        }
+                    var exprValue = _engine.EvaluateExpression(property.Value);
+                    var propValue = _engine.GetValue(exprValue, true);
+                    propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable);
+                }
+                else if (property.Kind == PropertyKind.Get || property.Kind == PropertyKind.Set)
+                {
+                    var function = property.Value as IFunction;
 
-                        ScriptFunctionInstance set;
-                        using (new StrictModeScope(setter.Strict))
-                        {
-                            set = new ScriptFunctionInstance(
-                                _engine,
-                                setter,
-                                _engine.ExecutionContext.LexicalEnvironment,
-                                StrictModeScope.IsStrictModeCode
-                            );
-                        }
+                    if (function == null)
+                    {
+                        ExceptionHelper.ThrowSyntaxError(_engine);
+                    }
 
-                        propDesc = new GetSetPropertyDescriptor(get: null, set: set, enumerableConfigurable);
-                        break;
+                    ScriptFunctionInstance functionInstance;
+                    using (new StrictModeScope(function.Strict))
+                    {
+                        functionInstance = new ScriptFunctionInstance(
+                            _engine,
+                            function,
+                            _engine.ExecutionContext.LexicalEnvironment,
+                            StrictModeScope.IsStrictModeCode
+                        );
+                    }
 
-                    default:
-                        ExceptionHelper.ThrowArgumentOutOfRangeException();
-                        return null;
+                    propDesc = new GetSetPropertyDescriptor(
+                        get: property.Kind == PropertyKind.Get ? functionInstance : null,
+                        set: property.Kind == PropertyKind.Set ? functionInstance : null,
+                        PropertyFlag.Enumerable | PropertyFlag.Configurable);
+                }
+                else
+                {
+                    ExceptionHelper.ThrowArgumentOutOfRangeException();
+                    return null;
                 }
 
                 if (previous != PropertyDescriptor.Undefined)
@@ -731,9 +711,14 @@ namespace Jint.Runtime
                             ExceptionHelper.ThrowSyntaxError(_engine);
                         }
                     }
-                }
 
-                obj.DefineOwnProperty(propName, propDesc, false);
+                    obj.DefineOwnProperty(propName, propDesc, false);
+                }
+                else
+                {
+                    // do faster direct set
+                    obj._properties[propName] = propDesc;
+                }
             }
 
             return obj;
@@ -1096,9 +1081,7 @@ namespace Jint.Runtime
             out bool cacheable)
         {
             cacheable = true;
-            var count = expressionArguments.Count;
-
-            for (var i = 0; i < count; i++)
+            for (var i = 0; i < (uint) targetArray.Length; i++)
             {
                 var argument = (Expression) expressionArguments[i];
                 targetArray[i] = _engine.GetValue(_engine.EvaluateExpression(argument), true);