Parcourir la source

Small performance optimization to string handling (#1358)

Marko Lahma il y a 2 ans
Parent
commit
402a431d39

+ 4 - 1
Jint/Native/Array/ArrayPrototype.cs

@@ -1245,7 +1245,10 @@ namespace Jint.Native.Array
             sb.Builder.Append(s);
             for (uint k = 1; k < len; k++)
             {
-                sb.Builder.Append(sep);
+                if (sep != "")
+                {
+                    sb.Builder.Append(sep);
+                }
                 sb.Builder.Append(StringFromJsValue(o.Get(k)));
             }
 

+ 0 - 11
Jint/Native/JsString.cs

@@ -196,17 +196,6 @@ public class JsString : JsValue, IEquatable<JsString>
         return _value;
     }
 
-    public JsArray ToArray(Engine engine)
-    {
-        var array = engine.Realm.Intrinsics.Array.ArrayCreate((uint) _value.Length);
-        for (int i = 0; i < _value.Length; ++i)
-        {
-            array.SetIndexValue((uint) i, _value[i], updateLength: false);
-        }
-
-        return array;
-    }
-
     internal int IndexOf(string value, StringComparison comparisonType)
     {
         return ToString().IndexOf(value, comparisonType);

+ 9 - 0
Jint/Native/String/StringConstructor.cs

@@ -55,7 +55,16 @@ namespace Jint.Native.String
                 return JsString.Empty;
             }
 
+            if (arguments.Length == 1)
+            {
+                return JsString.Create((char) TypeConverter.ToUint16(arguments[0]));
+            }
+
+#if NETSTANDARD2_1_OR_GREATER
+            var elements = length < 512 ? stackalloc char[length] : new char[length];
+#else
             var elements = new char[length];
+#endif
             for (var i = 0; i < elements.Length; i++ )
             {
                 var nextCu = TypeConverter.ToUint16(arguments[i]);

+ 13 - 25
Jint/Native/String/StringPrototype.cs

@@ -453,7 +453,7 @@ namespace Jint.Native.String
                 return JsString.Empty;
             }
 
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             var end = TypeConverter.ToNumber(arguments.At(1));
             if (double.IsPositiveInfinity(end))
             {
@@ -477,7 +477,7 @@ namespace Jint.Native.String
                 return JsString.Create(s[from]);
             }
 
-            return new JsString(s.Substring(from, span));
+            return s.Substring(from, span);
         }
 
         private JsValue Search(JsValue thisObj, JsValue[] arguments)
@@ -495,7 +495,7 @@ namespace Jint.Native.String
             }
 
             var rx = (RegExpInstance) _realm.Intrinsics.RegExp.Construct(new[] {regex});
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             return _engine.Invoke(rx, GlobalSymbolRegistry.Search, new JsValue[] { s });
         }
 
@@ -665,7 +665,7 @@ namespace Jint.Native.String
 
             var rx = (RegExpInstance) _realm.Intrinsics.RegExp.Construct(new[] {regex});
 
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             return _engine.Invoke(rx, GlobalSymbolRegistry.Match, new JsValue[] { s });
         }
 
@@ -692,7 +692,7 @@ namespace Jint.Native.String
                 }
             }
 
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             var rx = (RegExpInstance) _realm.Intrinsics.RegExp.Construct(new[] { regex, "g" });
 
             return _engine.Invoke(rx, GlobalSymbolRegistry.MatchAll, new JsValue[] { s });
@@ -787,30 +787,18 @@ namespace Jint.Native.String
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
-            // try to hint capacity if possible
-            int capacity = 0;
-            for (int i = 0; i < arguments.Length; ++i)
-            {
-                if (arguments[i].Type == Types.String)
-                {
-                    capacity += arguments[i].ToString().Length;
-                }
-            }
-
-            var value = TypeConverter.ToString(thisObj);
-            capacity += value.Length;
-            if (!(thisObj is JsString jsString))
+            if (thisObj is not JsString jsString)
             {
-                jsString = new JsString.ConcatenatedString(value, capacity);
+                jsString = new JsString.ConcatenatedString(TypeConverter.ToString(thisObj));
             }
             else
             {
-                jsString = jsString.EnsureCapacity(capacity);
+                jsString = jsString.EnsureCapacity(0);
             }
 
-            for (int i = 0; i < arguments.Length; i++)
+            foreach (var argument in arguments)
             {
-                jsString = jsString.Append(arguments[i]);
+                jsString = jsString.Append(argument);
             }
 
             return jsString;
@@ -821,8 +809,8 @@ namespace Jint.Native.String
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
             JsValue pos = arguments.Length > 0 ? arguments[0] : 0;
-            var s = TypeConverter.ToString(thisObj);
-            var position = (int)TypeConverter.ToInteger(pos);
+            var s = TypeConverter.ToJsString(thisObj);
+            var position = (int) TypeConverter.ToInteger(pos);
             if (position < 0 || position >= s.Length)
             {
                 return JsNumber.DoubleNaN;
@@ -860,7 +848,7 @@ namespace Jint.Native.String
         private JsValue CharAt(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             var position = TypeConverter.ToInteger(arguments.At(0));
             var size = s.Length;
             if (position >= size || position < 0)

+ 56 - 49
Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs

@@ -44,8 +44,9 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
 
             var engine = context.Engine;
-            var lval = context.Engine.GetValue(lref, false);
+            var originalLeftValue = context.Engine.GetValue(lref, false);
             var handledByOverload = false;
+            JsValue? newLeftValue = null;
 
             if (context.OperatorOverloadingAllowed)
             {
@@ -94,14 +95,15 @@ namespace Jint.Runtime.Interpreter.Expressions
                 if (operatorClrName != null)
                 {
                     var rval = _right.GetValue(context);
-                    if (JintBinaryExpression.TryOperatorOverloading(context, lval, rval, operatorClrName, out var result))
+                    if (JintBinaryExpression.TryOperatorOverloading(context, originalLeftValue, rval, operatorClrName, out var result))
                     {
-                        lval = JsValue.FromObject(context.Engine, result);
+                        newLeftValue = JsValue.FromObject(context.Engine, result);
                         handledByOverload = true;
                     }
                 }
             }
 
+            var wasMutatedInPlace = false;
             if (!handledByOverload)
             {
                 switch (_operator)
@@ -109,31 +111,32 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.PlusAssign:
                     {
                         var rval = _right.GetValue(context);
-                        if (AreIntegerOperands(lval, rval))
+                        if (AreIntegerOperands(originalLeftValue, rval))
                         {
-                            lval = (long) lval.AsInteger() + rval.AsInteger();
+                            newLeftValue = (long) originalLeftValue.AsInteger() + rval.AsInteger();
                         }
                         else
                         {
-                            var lprim = TypeConverter.ToPrimitive(lval);
+                            var lprim = TypeConverter.ToPrimitive(originalLeftValue);
                             var rprim = TypeConverter.ToPrimitive(rval);
 
                             if (lprim.IsString() || rprim.IsString())
                             {
-                                if (!(lprim is JsString jsString))
+                                wasMutatedInPlace = lprim is JsString.ConcatenatedString;
+                                if (lprim is not JsString jsString)
                                 {
                                     jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
                                 }
 
-                                lval = jsString.Append(rprim);
+                                newLeftValue = jsString.Append(rprim);
                             }
-                            else if (!AreIntegerOperands(lval, rval))
+                            else if (!AreIntegerOperands(originalLeftValue, rval))
                             {
-                                lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
+                                newLeftValue = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
                             }
                             else
                             {
-                                lval = TypeConverter.ToBigInt(lprim) + TypeConverter.ToBigInt(rprim);
+                                newLeftValue = TypeConverter.ToBigInt(lprim) + TypeConverter.ToBigInt(rprim);
                             }
                         }
 
@@ -143,17 +146,17 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.MinusAssign:
                     {
                         var rval = _right.GetValue(context);
-                        if (AreIntegerOperands(lval, rval))
+                        if (AreIntegerOperands(originalLeftValue, rval))
                         {
-                            lval = JsNumber.Create(lval.AsInteger() - rval.AsInteger());
+                            newLeftValue = JsNumber.Create(originalLeftValue.AsInteger() - rval.AsInteger());
                         }
-                        else if (!AreIntegerOperands(lval, rval))
+                        else if (!AreIntegerOperands(originalLeftValue, rval))
                         {
-                            lval = JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval));
+                            newLeftValue = JsNumber.Create(TypeConverter.ToNumber(originalLeftValue) - TypeConverter.ToNumber(rval));
                         }
                         else
                         {
-                            lval = JsNumber.Create(TypeConverter.ToBigInt(lval) - TypeConverter.ToBigInt(rval));
+                            newLeftValue = JsNumber.Create(TypeConverter.ToBigInt(originalLeftValue) - TypeConverter.ToBigInt(rval));
                         }
 
                         break;
@@ -162,21 +165,21 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.TimesAssign:
                     {
                         var rval = _right.GetValue(context);
-                        if (AreIntegerOperands(lval, rval))
+                        if (AreIntegerOperands(originalLeftValue, rval))
                         {
-                            lval = (long) lval.AsInteger() * rval.AsInteger();
+                            newLeftValue = (long) originalLeftValue.AsInteger() * rval.AsInteger();
                         }
-                        else if (lval.IsUndefined() || rval.IsUndefined())
+                        else if (originalLeftValue.IsUndefined() || rval.IsUndefined())
                         {
-                            lval = JsValue.Undefined;
+                            newLeftValue = JsValue.Undefined;
                         }
-                        else if (!AreIntegerOperands(lval, rval))
+                        else if (!AreIntegerOperands(originalLeftValue, rval))
                         {
-                            lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval);
+                            newLeftValue = TypeConverter.ToNumber(originalLeftValue) * TypeConverter.ToNumber(rval);
                         }
                         else
                         {
-                            lval = TypeConverter.ToBigInt(lval) * TypeConverter.ToBigInt(rval);
+                            newLeftValue = TypeConverter.ToBigInt(originalLeftValue) * TypeConverter.ToBigInt(rval);
                         }
 
                         break;
@@ -185,24 +188,24 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.DivideAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = Divide(context, lval, rval);
+                        newLeftValue = Divide(context, originalLeftValue, rval);
                         break;
                     }
 
                     case AssignmentOperator.ModuloAssign:
                     {
                         var rval = _right.GetValue(context);
-                        if (lval.IsUndefined() || rval.IsUndefined())
+                        if (originalLeftValue.IsUndefined() || rval.IsUndefined())
                         {
-                            lval = JsValue.Undefined;
+                            newLeftValue = JsValue.Undefined;
                         }
-                        else if (!AreIntegerOperands(lval, rval))
+                        else if (!AreIntegerOperands(originalLeftValue, rval))
                         {
-                            lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
+                            newLeftValue = TypeConverter.ToNumber(originalLeftValue) % TypeConverter.ToNumber(rval);
                         }
                         else
                         {
-                            lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
+                            newLeftValue = TypeConverter.ToNumber(originalLeftValue) % TypeConverter.ToNumber(rval);
                         }
 
                         break;
@@ -211,87 +214,87 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.BitwiseAndAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = TypeConverter.ToInt32(lval) & TypeConverter.ToInt32(rval);
+                        newLeftValue = TypeConverter.ToInt32(originalLeftValue) & TypeConverter.ToInt32(rval);
                         break;
                     }
 
                     case AssignmentOperator.BitwiseOrAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = TypeConverter.ToInt32(lval) | TypeConverter.ToInt32(rval);
+                        newLeftValue = TypeConverter.ToInt32(originalLeftValue) | TypeConverter.ToInt32(rval);
                         break;
                     }
 
                     case AssignmentOperator.BitwiseXorAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = TypeConverter.ToInt32(lval) ^ TypeConverter.ToInt32(rval);
+                        newLeftValue = TypeConverter.ToInt32(originalLeftValue) ^ TypeConverter.ToInt32(rval);
                         break;
                     }
 
                     case AssignmentOperator.LeftShiftAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = TypeConverter.ToInt32(lval) << (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                        newLeftValue = TypeConverter.ToInt32(originalLeftValue) << (int) (TypeConverter.ToUint32(rval) & 0x1F);
                         break;
                     }
 
                     case AssignmentOperator.RightShiftAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                        newLeftValue = TypeConverter.ToInt32(originalLeftValue) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
                         break;
                     }
 
                     case AssignmentOperator.UnsignedRightShiftAssign:
                     {
                         var rval = _right.GetValue(context);
-                        lval = (uint) TypeConverter.ToInt32(lval) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
+                        newLeftValue = (uint) TypeConverter.ToInt32(originalLeftValue) >> (int) (TypeConverter.ToUint32(rval) & 0x1F);
                         break;
                     }
 
                     case AssignmentOperator.NullishAssign:
                     {
-                        if (!lval.IsNullOrUndefined())
+                        if (!originalLeftValue.IsNullOrUndefined())
                         {
-                            return lval;
+                            return originalLeftValue;
                         }
 
                         var rval = NamedEvaluation(context, _right);
-                        lval = rval;
+                        newLeftValue = rval;
                         break;
                     }
 
                     case AssignmentOperator.AndAssign:
                     {
-                        if (!TypeConverter.ToBoolean(lval))
+                        if (!TypeConverter.ToBoolean(originalLeftValue))
                         {
-                            return lval;
+                            return originalLeftValue;
                         }
 
                         var rval = NamedEvaluation(context, _right);
-                        lval = rval;
+                        newLeftValue = rval;
                         break;
                     }
 
                     case AssignmentOperator.OrAssign:
                     {
-                        if (TypeConverter.ToBoolean(lval))
+                        if (TypeConverter.ToBoolean(originalLeftValue))
                         {
-                            return lval;
+                            return originalLeftValue;
                         }
 
                         var rval = NamedEvaluation(context, _right);
-                        lval = rval;
+                        newLeftValue = rval;
                         break;
                     }
 
                     case AssignmentOperator.ExponentiationAssign:
                     {
                         var rval = _right.GetValue(context);
-                        if (!lval.IsBigInt() && !rval.IsBigInt())
+                        if (!originalLeftValue.IsBigInt() && !rval.IsBigInt())
                         {
-                            lval = JsNumber.Create(Math.Pow(TypeConverter.ToNumber(lval), TypeConverter.ToNumber(rval)));
+                            newLeftValue = JsNumber.Create(Math.Pow(TypeConverter.ToNumber(originalLeftValue), TypeConverter.ToNumber(rval)));
                         }
                         else
                         {
@@ -300,7 +303,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                             {
                                 ExceptionHelper.ThrowTypeError(context.Engine.Realm, "Cannot do exponentation with exponent not fitting int32");
                             }
-                            lval = JsBigInt.Create(BigInteger.Pow(TypeConverter.ToBigInt(lval), (int) exponent));
+                            newLeftValue = JsBigInt.Create(BigInteger.Pow(TypeConverter.ToBigInt(originalLeftValue), (int) exponent));
                         }
 
                         break;
@@ -312,10 +315,14 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
             }
 
-            engine.PutValue(lref, lval);
+            // if we did string concatenation in-place, we don't need to update records, objects might have evil setters
+            if (!wasMutatedInPlace || lref.GetBase() is not EnvironmentRecord)
+            {
+                engine.PutValue(lref, newLeftValue!);
+            }
 
             engine._referencePool.Return(lref);
-            return lval;
+            return newLeftValue!;
         }
 
         private JsValue NamedEvaluation(EvaluationContext context, JintExpression expression)

+ 7 - 6
Jint/Runtime/Interpreter/Statements/ProbablyBlockStatement.cs

@@ -11,28 +11,29 @@ namespace Jint.Runtime.Interpreter.Statements;
 [StructLayout(LayoutKind.Auto)]
 internal readonly struct ProbablyBlockStatement
 {
-    private readonly JintStatement  _target;
+    private readonly JintStatement? _statement = null;
+    private readonly JintBlockStatement? _blockStatement = null;
 
     public ProbablyBlockStatement(Statement statement)
     {
         if (statement is BlockStatement blockStatement)
         {
-            _target = new JintBlockStatement(blockStatement);
+            _blockStatement = new JintBlockStatement(blockStatement);
         }
         else
         {
-            _target = JintStatement.Build(statement);
+            _statement = JintStatement.Build(statement);
         }
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public Completion Execute(EvaluationContext context)
     {
-        if (_target is JintBlockStatement blockStatement)
+        if (_blockStatement is not null)
         {
-            return blockStatement.ExecuteBlock(context);
+            return _blockStatement.ExecuteBlock(context);
         }
 
-        return _target.Execute(context);
+        return _statement!.Execute(context);
     }
 }