Selaa lähdekoodia

Remove some unnecessary JsString allocations (#1728)

Marko Lahma 1 vuosi sitten
vanhempi
commit
4b23f03c52

+ 0 - 1
Jint.Tests/Runtime/FunctionTests.cs

@@ -1,5 +1,4 @@
 using Jint.Native;
-using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Runtime;
 using Jint.Runtime.Interop;

+ 0 - 1
Jint.Tests/Runtime/RegExpTests.cs

@@ -1,6 +1,5 @@
 using System.Text.RegularExpressions;
 using Jint.Native;
-using Jint.Native.Array;
 
 namespace Jint.Tests.Runtime;
 

+ 1 - 1
Jint/Native/AggregateError/AggregateErrorConstructor.cs

@@ -49,7 +49,7 @@ internal sealed class AggregateErrorConstructor : Constructor
         if (!message.IsUndefined())
         {
             var msg = TypeConverter.ToString(message);
-            o.CreateNonEnumerableDataPropertyOrThrow("message", msg);
+            o.CreateNonEnumerableDataPropertyOrThrow(CommonProperties.Message, msg);
         }
 
         o.InstallErrorCause(options);

+ 5 - 6
Jint/Native/Array/ArrayConstructor.cs

@@ -82,22 +82,21 @@ namespace Jint.Native.Array
                 return instance;
             }
 
-            var objectInstance = TypeConverter.ToObject(_realm, items);
-            if (objectInstance is IObjectWrapper { Target: IEnumerable enumerable })
+            if (items is IObjectWrapper { Target: IEnumerable enumerable })
             {
                 return ConstructArrayFromIEnumerable(enumerable);
             }
 
-            return ConstructArrayFromArrayLike(thisObject, objectInstance, callable, thisArg);
+            var source = ArrayOperations.For(_realm, items, forWrite: false);
+            return ConstructArrayFromArrayLike(thisObject, source, callable, thisArg);
         }
 
         private ObjectInstance ConstructArrayFromArrayLike(
             JsValue thisObj,
-            ObjectInstance objectInstance,
+            ArrayOperations source,
             ICallable? callable,
             JsValue thisArg)
         {
-            var source = ArrayOperations.For(objectInstance);
             var length = source.GetLength();
 
             ObjectInstance a;
@@ -311,7 +310,7 @@ namespace Jint.Native.Array
                         break;
                     case JsArray array:
                         // direct copy
-                        instance = (JsArray) ConstructArrayFromArrayLike(Undefined, array, null, this);
+                        instance = (JsArray) ConstructArrayFromArrayLike(Undefined, ArrayOperations.For(array), callable: null, this);
                         break;
                     default:
                         instance = ArrayCreate(capacity, prototypeObject);

+ 81 - 15
Jint/Native/Array/ArrayOperations.cs

@@ -1,6 +1,7 @@
 using System.Collections;
 using Jint.Native.Number;
 using Jint.Native.Object;
+using Jint.Native.String;
 using Jint.Native.TypedArray;
 using Jint.Runtime;
 
@@ -11,25 +12,37 @@ namespace Jint.Native.Array
         protected internal const ulong MaxArrayLength = 4294967295;
         protected internal const ulong MaxArrayLikeLength = NumberConstructor.MaxSafeInteger;
 
+        public static ArrayOperations For(Realm realm, JsValue value, bool forWrite)
+        {
+            if (!forWrite)
+            {
+                if (value.IsString())
+                {
+                    return new JsStringOperations(realm, (JsString) value);
+                }
+
+                if (value is StringInstance stringInstance)
+                {
+                    return new JsStringOperations(realm, stringInstance);
+                }
+            }
+
+            return For(TypeConverter.ToObject(realm, value));
+        }
+
         public static ArrayOperations For(ObjectInstance instance)
         {
             if (instance is JsArray { CanUseFastAccess: true } arrayInstance)
             {
-                return new ArrayInstanceOperations(arrayInstance);
+                return new JsArrayOperations(arrayInstance);
             }
 
             if (instance is JsTypedArray typedArrayInstance)
             {
-                return new TypedArrayInstanceOperations(typedArrayInstance);
+                return new JsTypedArrayOperations(typedArrayInstance);
             }
 
-            return new ObjectInstanceOperations(instance);
-        }
-
-        public static ArrayOperations For(Realm realm, JsValue thisObj)
-        {
-            var instance = TypeConverter.ToObject(realm, thisObj);
-            return For(instance);
+            return new ObjectOperations(instance);
         }
 
         public abstract ObjectInstance Target { get; }
@@ -135,9 +148,9 @@ namespace Jint.Native.Array
             }
         }
 
-        private sealed class ObjectInstanceOperations : ArrayOperations<ObjectInstance>
+        private sealed class ObjectOperations : ArrayOperations<ObjectInstance>
         {
-            public ObjectInstanceOperations(ObjectInstance target) : base(target)
+            public ObjectOperations(ObjectInstance target) : base(target)
             {
             }
 
@@ -204,9 +217,9 @@ namespace Jint.Native.Array
             public override bool HasProperty(ulong index) => Target.HasProperty(index);
         }
 
-        private sealed class ArrayInstanceOperations : ArrayOperations<JsArray>
+        private sealed class JsArrayOperations : ArrayOperations<JsArray>
         {
-            public ArrayInstanceOperations(JsArray target) : base(target)
+            public JsArrayOperations(JsArray target) : base(target)
             {
             }
 
@@ -274,11 +287,11 @@ namespace Jint.Native.Array
             public override bool HasProperty(ulong index) => _target.HasProperty(index);
         }
 
-        private sealed class TypedArrayInstanceOperations : ArrayOperations
+        private sealed class JsTypedArrayOperations : ArrayOperations
         {
             private readonly JsTypedArray _target;
 
-            public TypedArrayInstanceOperations(JsTypedArray target)
+            public JsTypedArrayOperations(JsTypedArray target)
             {
                 _target = target;
             }
@@ -339,6 +352,59 @@ namespace Jint.Native.Array
             public override bool HasProperty(ulong index) => _target.HasProperty(index);
         }
 
+        private sealed class JsStringOperations : ArrayOperations
+        {
+            private readonly Realm _realm;
+            private readonly JsString _target;
+            private ObjectInstance? _wrappedTarget;
+
+            public JsStringOperations(Realm realm, JsString target)
+            {
+                _realm = realm;
+                _target = target;
+            }
+
+            public JsStringOperations(Realm realm, StringInstance stringInstance) : this(realm, stringInstance.StringData)
+            {
+                _wrappedTarget = stringInstance;
+            }
+
+            public override ObjectInstance Target => _wrappedTarget ??= _realm.Intrinsics.String.Construct(_target);
+
+            public override ulong GetSmallestIndex(ulong length) => 0;
+
+            public override uint GetLength() => (uint) _target.Length;
+
+            public override ulong GetLongLength() => GetLength();
+
+            public override void SetLength(ulong length) => throw new NotSupportedException();
+
+            public override void EnsureCapacity(ulong capacity)
+            {
+            }
+
+            public override JsValue Get(ulong index) => index < (ulong) _target.Length ? _target[(int) index] : JsValue.Undefined;
+
+            public override bool TryGetValue(ulong index, out JsValue value)
+            {
+                if (index < (ulong) _target.Length)
+                {
+                    value = _target[(int) index];
+                    return true;
+                }
+
+                value = JsValue.Undefined;
+                return false;
+            }
+
+            public override bool HasProperty(ulong index) => index < (ulong) _target.Length;
+
+            public override void CreateDataPropertyOrThrow(ulong index, JsValue value) => throw new NotSupportedException();
+
+            public override void Set(ulong index, JsValue value, bool updateLength = false, bool throwOnError = true) => throw new NotSupportedException();
+
+            public override void DeletePropertyOrThrow(ulong index) => throw new NotSupportedException();
+        }
     }
 
     /// <summary>

+ 18 - 18
Jint/Native/Array/ArrayPrototype.cs

@@ -323,7 +323,7 @@ namespace Jint.Native.Array
         /// </summary>
         private JsValue LastIndexOf(JsValue thisObject, JsValue[] arguments)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLongLength();
             if (len == 0)
             {
@@ -379,7 +379,7 @@ namespace Jint.Native.Array
             var callbackfn = arguments.At(0);
             var initialValue = arguments.At(1);
 
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLength();
 
             var callable = GetCallable(callbackfn);
@@ -441,7 +441,7 @@ namespace Jint.Native.Array
             var callbackfn = arguments.At(0);
             var thisArg = arguments.At(1);
 
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLength();
 
             var callable = GetCallable(callbackfn);
@@ -484,7 +484,7 @@ namespace Jint.Native.Array
                 return arrayInstance.Map(arguments);
             }
 
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLongLength();
 
             if (len > ArrayOperations.MaxArrayLength)
@@ -639,7 +639,7 @@ namespace Jint.Native.Array
             var callbackfn = arguments.At(0);
             var thisArg = arguments.At(1);
 
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLength();
 
             var callable = GetCallable(callbackfn);
@@ -665,7 +665,7 @@ namespace Jint.Native.Array
         /// </summary>
         private JsValue Includes(JsValue thisObject, JsValue[] arguments)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = (long) o.GetLongLength();
 
             if (len == 0)
@@ -722,7 +722,7 @@ namespace Jint.Native.Array
         /// </summary>
         private JsValue Every(JsValue thisObject, JsValue[] arguments)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             ulong len = o.GetLongLength();
 
             if (len == 0)
@@ -759,7 +759,7 @@ namespace Jint.Native.Array
         /// </summary>
         private JsValue IndexOf(JsValue thisObject, JsValue[] arguments)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLongLength();
             if (len == 0)
             {
@@ -896,7 +896,7 @@ namespace Jint.Native.Array
             var deleteCount = arguments.At(1);
 
             var obj = TypeConverter.ToObject(_realm, thisObject);
-            var o = ArrayOperations.For(_realm, obj);
+            var o = ArrayOperations.For(_realm, obj, forWrite: false);
             var len = o.GetLongLength();
             var relativeStart = TypeConverter.ToInteger(start);
 
@@ -1012,7 +1012,7 @@ namespace Jint.Native.Array
         /// </summary>
         private JsValue Unshift(JsValue thisObject, JsValue[] arguments)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
             var len = o.GetLongLength();
             var argCount = (uint) arguments.Length;
 
@@ -1104,7 +1104,7 @@ namespace Jint.Native.Array
             var start = arguments.At(0);
             var end = arguments.At(1);
 
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLongLength();
 
             var relativeStart = TypeConverter.ToInteger(start);
@@ -1164,7 +1164,7 @@ namespace Jint.Native.Array
 
         private JsValue Shift(JsValue thisObject, JsValue[] arg2)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
             var len = o.GetLength();
             if (len == 0)
             {
@@ -1197,7 +1197,7 @@ namespace Jint.Native.Array
         /// </summary>
         private JsValue Reverse(JsValue thisObject, JsValue[] arguments)
         {
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
             var len = o.GetLongLength();
             var middle = (ulong) System.Math.Floor(len / 2.0);
             uint lower = 0;
@@ -1241,7 +1241,7 @@ namespace Jint.Native.Array
         private JsValue Join(JsValue thisObject, JsValue[] arguments)
         {
             var separator = arguments.At(0);
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLength();
 
             var sep = TypeConverter.ToString(separator.IsUndefined() ? JsString.CommaString : separator);
@@ -1281,7 +1281,7 @@ namespace Jint.Native.Array
 
         private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
         {
-            var array = ArrayOperations.For(_realm, thisObject);
+            var array = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = array.GetLength();
             const string Separator = ",";
             if (len == 0)
@@ -1434,7 +1434,7 @@ namespace Jint.Native.Array
             var start = arguments.At(0);
             var deleteCount = arguments.At(1);
 
-            var o = ArrayOperations.For(_realm, TypeConverter.ToObject(_realm, thisObject));
+            var o = ArrayOperations.For(_realm, TypeConverter.ToObject(_realm, thisObject), forWrite: false);
             var len = o.GetLongLength();
             var relativeStart = TypeConverter.ToIntegerOrInfinity(start);
 
@@ -1554,7 +1554,7 @@ namespace Jint.Native.Array
             var callbackfn = arguments.At(0);
             var initialValue = arguments.At(1);
 
-            var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject));
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: false);
             var len = o.GetLongLength();
 
             var callable = GetCallable(callbackfn);
@@ -1640,7 +1640,7 @@ namespace Jint.Native.Array
                 return array.Pop();
             }
 
-            var o = ArrayOperations.For(_realm, thisObject);
+            var o = ArrayOperations.For(_realm, thisObject, forWrite: true);
             ulong len = o.GetLongLength();
             if (len == 0)
             {

+ 0 - 1
Jint/Native/Constructor.cs

@@ -1,4 +1,3 @@
-using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Runtime;
 

+ 2 - 3
Jint/Native/Error/ErrorConstructor.cs

@@ -48,9 +48,8 @@ namespace Jint.Native.Error
             var jsValue = arguments.At(0);
             if (!jsValue.IsUndefined())
             {
-                var msg = TypeConverter.ToString(jsValue);
-                var msgDesc = new PropertyDescriptor(msg, true, false, true);
-                o.DefinePropertyOrThrow("message", msgDesc);
+                var msg = TypeConverter.ToJsString(jsValue);
+                o.CreateNonEnumerableDataPropertyOrThrow(CommonProperties.Message, msg);
             }
 
             var stackString = BuildStackString();

+ 18 - 0
Jint/Native/JsString.cs

@@ -1,6 +1,9 @@
 using System.Collections.Concurrent;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Text;
+using Jint.Native.Generator;
+using Jint.Native.Iterator;
 using Jint.Runtime;
 
 namespace Jint.Native;
@@ -286,6 +289,21 @@ public class JsString : JsValue, IEquatable<JsString>, IEquatable<string>
         return ToString().Substring(startIndex);
     }
 
+    internal override bool TryGetIterator(
+        Realm realm,
+        [NotNullWhen(true)] out IteratorInstance? iterator,
+        GeneratorKind hint = GeneratorKind.Sync,
+        ICallable? method = null)
+    {
+        if (realm.Intrinsics.String.PrototypeObject.HasOriginalIterator)
+        {
+            iterator = new IteratorInstance.StringIterator(realm.GlobalEnv._engine, ToString());
+            return true;
+        }
+
+        return base.TryGetIterator(realm, out iterator, hint, method);
+    }
+
     public sealed override bool Equals(object? obj) => Equals(obj as JsString);
 
     public sealed override bool Equals(JsValue? other) => Equals(other as JsString);

+ 5 - 1
Jint/Native/JsValue.cs

@@ -68,7 +68,11 @@ namespace Jint.Native
         }
 
         [Pure]
-        internal bool TryGetIterator(Realm realm, [NotNullWhen(true)] out IteratorInstance? iterator, GeneratorKind hint = GeneratorKind.Sync, ICallable? method = null)
+        internal virtual bool TryGetIterator(
+            Realm realm,
+            [NotNullWhen(true)] out IteratorInstance? iterator,
+            GeneratorKind hint = GeneratorKind.Sync,
+            ICallable? method = null)
         {
             var obj = TypeConverter.ToObject(realm, this);
 

+ 10 - 5
Jint/Native/RegExp/RegExpPrototype.cs

@@ -24,6 +24,11 @@ namespace Jint.Native.RegExp
         private static readonly JsString DefaultSource = new("(?:)");
         internal static readonly JsString PropertyFlags = new("flags");
         private static readonly JsString PropertyGroups = new("groups");
+        private static readonly JsString PropertyIgnoreCase = new("ignoreCase");
+        private static readonly JsString PropertyMultiline = new("multiline");
+        private static readonly JsString PropertyDotAll = new("dotAll");
+        private static readonly JsString PropertyUnicode = new("unicode");
+        private static readonly JsString PropertyUnicodeSets = new("unicodeSets");
 
         private readonly RegExpConstructor _constructor;
         private readonly Func<JsValue, JsValue[], JsValue> _defaultExec;
@@ -591,11 +596,11 @@ namespace Jint.Native.RegExp
 
             var result = AddFlagIfPresent(r, "hasIndices", 'd', "");
             result = AddFlagIfPresent(r, PropertyGlobal, 'g', result);
-            result = AddFlagIfPresent(r, "ignoreCase", 'i', result);
-            result = AddFlagIfPresent(r, "multiline", 'm', result);
-            result = AddFlagIfPresent(r, "dotAll", 's', result);
-            result = AddFlagIfPresent(r, "unicode", 'u', result);
-            result = AddFlagIfPresent(r, "unicodeSets", 'v', result);
+            result = AddFlagIfPresent(r, PropertyIgnoreCase, 'i', result);
+            result = AddFlagIfPresent(r, PropertyMultiline, 'm', result);
+            result = AddFlagIfPresent(r, PropertyDotAll, 's', result);
+            result = AddFlagIfPresent(r, PropertyUnicode, 'u', result);
+            result = AddFlagIfPresent(r, PropertyUnicodeSets, 'v', result);
             result = AddFlagIfPresent(r, PropertySticky, 'y', result);
 
             return result;

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

@@ -204,11 +204,6 @@ namespace Jint.Native.String
             return StringCreate(s, GetPrototypeFromConstructor(newTarget, static intrinsics => intrinsics.String.PrototypeObject));
         }
 
-        public StringInstance Construct(string value)
-        {
-            return Construct(JsString.Create(value));
-        }
-
         public StringInstance Construct(JsString value)
         {
             return StringCreate(value, PrototypeObject);

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

@@ -22,6 +22,7 @@ namespace Jint.Native.String
     {
         private readonly Realm _realm;
         private readonly StringConstructor _constructor;
+        internal ClrFunction? _originalIteratorFunction;
 
         internal StringPrototype(
             Engine engine,
@@ -86,13 +87,16 @@ namespace Jint.Native.String
             };
             SetProperties(properties);
 
+            _originalIteratorFunction = new ClrFunction(Engine, "[Symbol.iterator]", Iterator, 0, lengthFlags);
             var symbols = new SymbolDictionary(1)
             {
-                [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunction(Engine, "[Symbol.iterator]", Iterator, 0, lengthFlags), propertyFlags)
+                [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(_originalIteratorFunction, propertyFlags)
             };
             SetSymbols(symbols);
         }
 
+        internal override bool HasOriginalIterator => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _originalIteratorFunction);
+
         private ObjectInstance Iterator(JsValue thisObject, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(_engine, thisObject);

+ 1 - 1
Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs

@@ -1100,7 +1100,7 @@ namespace Jint.Native.TypedArray
             targetBuffer.AssertNotDetached();
 
             var targetLength = target._arrayLength;
-            var src = ArrayOperations.For(TypeConverter.ToObject(_realm, source));
+            var src = ArrayOperations.For(_realm, source, forWrite: false);
             var srcLength = src.GetLength();
 
             if (double.IsNegativeInfinity(targetOffset))

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

@@ -20,7 +20,7 @@ public sealed class ClrFunction : Function, IEquatable<ClrFunction>
         Func<JsValue, JsValue[], JsValue> func,
         int length = 0,
         PropertyFlag lengthFlags = PropertyFlag.AllForbidden)
-        : base(engine, engine.Realm, new JsString(name))
+        : base(engine, engine.Realm, JsString.CachedCreate(name))
     {
         _func = func;
 

+ 6 - 5
Jint/Runtime/TypeConverter.cs

@@ -1009,19 +1009,20 @@ namespace Jint.Runtime
         private static ObjectInstance ToObjectNonObject(Realm realm, JsValue value)
         {
             var type = value._type & ~InternalTypes.InternalFlags;
+            var intrinsics = realm.Intrinsics;
             switch (type)
             {
                 case InternalTypes.Boolean:
-                    return realm.Intrinsics.Boolean.Construct((JsBoolean) value);
+                    return intrinsics.Boolean.Construct((JsBoolean) value);
                 case InternalTypes.Number:
                 case InternalTypes.Integer:
-                    return realm.Intrinsics.Number.Construct((JsNumber) value);
+                    return intrinsics.Number.Construct((JsNumber) value);
                 case InternalTypes.BigInt:
-                    return realm.Intrinsics.BigInt.Construct((JsBigInt) value);
+                    return intrinsics.BigInt.Construct((JsBigInt) value);
                 case InternalTypes.String:
-                    return realm.Intrinsics.String.Construct(value.ToString());
+                    return intrinsics.String.Construct(value as JsString ?? JsString.Create(value.ToString()));
                 case InternalTypes.Symbol:
-                    return realm.Intrinsics.Symbol.Construct((JsSymbol) value);
+                    return intrinsics.Symbol.Construct((JsSymbol) value);
                 case InternalTypes.Null:
                 case InternalTypes.Undefined:
                     ExceptionHelper.ThrowTypeError(realm, "Cannot convert undefined or null to object");