Browse Source

Update test262 to latest and fix issues (#1151)

* use latest test262 commit
* implement __proto__
* track prototype changes to ensure valid fast array access
Marko Lahma 3 years ago
parent
commit
e0328f2cf4

+ 1 - 2
Jint.Tests.Test262/Test262Harness.settings.json

@@ -1,5 +1,5 @@
 {
 {
-  "SuiteGitSha": "91356f52f92691abe62c06d00677332212e99dc8",
+  "SuiteGitSha": "28b31c0bf1960878abb36ab8597a0cae224a684d",
   //"SuiteDirectory": "//mnt/c/work/test262",
   //"SuiteDirectory": "//mnt/c/work/test262",
   "TargetPath": "./Generated",
   "TargetPath": "./Generated",
   "Namespace": "Jint.Tests.Test262",
   "Namespace": "Jint.Tests.Test262",
@@ -7,7 +7,6 @@
   "ExcludedFeatures": [
   "ExcludedFeatures": [
     "__getter__",
     "__getter__",
     "__setter__",
     "__setter__",
-    "__proto__",
     "AggregateError",
     "AggregateError",
     "async-functions",
     "async-functions",
     "async-iteration",
     "async-iteration",

+ 13 - 10
Jint/Native/Array/ArrayConstructor.cs

@@ -213,29 +213,32 @@ namespace Jint.Native.Array
             if (thisObj.IsConstructor)
             if (thisObj.IsConstructor)
             {
             {
                 a = ((IConstructor) thisObj).Construct(new JsValue[] { len }, thisObj);
                 a = ((IConstructor) thisObj).Construct(new JsValue[] { len }, thisObj);
+            }
+            else
+            {
+                a = _realm.Intrinsics.Array.Construct(len);
+            }
 
 
+            if (a is ArrayInstance ai)
+            {
+                // faster for real arrays
                 for (uint k = 0; k < arguments.Length; k++)
                 for (uint k = 0; k < arguments.Length; k++)
                 {
                 {
                     var kValue = arguments[k];
                     var kValue = arguments[k];
-                    var key = JsString.Create(k);
-                    a.CreateDataPropertyOrThrow(key, kValue);
+                    ai.SetIndexValue(k, kValue, updateLength: k == arguments.Length - 1);
                 }
                 }
-
-                a.Set(CommonProperties.Length, len, true);
             }
             }
             else
             else
             {
             {
-                // faster for real arrays
-                ArrayInstance ai;
-                a = ai = _realm.Intrinsics.Array.Construct(len);
-
+                // slower version
                 for (uint k = 0; k < arguments.Length; k++)
                 for (uint k = 0; k < arguments.Length; k++)
                 {
                 {
                     var kValue = arguments[k];
                     var kValue = arguments[k];
-                    ai.SetIndexValue(k, kValue, updateLength: false);
+                    var key = JsString.Create(k);
+                    a.CreateDataPropertyOrThrow(key, kValue);
                 }
                 }
 
 
-                ai.SetLength((uint) arguments.Length);
+                a.Set(CommonProperties.Length, len, true);
             }
             }
 
 
             return a;
             return a;

+ 128 - 28
Jint/Native/Array/ArrayInstance.cs

@@ -18,6 +18,8 @@ namespace Jint.Native.Array
         internal PropertyDescriptor[] _dense;
         internal PropertyDescriptor[] _dense;
         private Dictionary<uint, PropertyDescriptor> _sparse;
         private Dictionary<uint, PropertyDescriptor> _sparse;
 
 
+        private ObjectChangeFlags _objectChangeFlags;
+
         public ArrayInstance(Engine engine, uint capacity = 0) : base(engine, ObjectClass.Array)
         public ArrayInstance(Engine engine, uint capacity = 0) : base(engine, ObjectClass.Array)
         {
         {
             if (capacity > engine.Options.Constraints.MaxArraySize)
             if (capacity > engine.Options.Constraints.MaxArraySize)
@@ -62,15 +64,59 @@ namespace Jint.Native.Array
             _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
             _length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
         }
         }
 
 
-        public override bool IsArrayLike => true;
+        public sealed override bool IsArrayLike => true;
 
 
-        public override bool IsArray() => true;
+        public sealed override bool IsArray() => true;
 
 
-        internal override bool HasOriginalIterator
+        internal sealed override bool HasOriginalIterator
             => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _engine.Realm.Intrinsics.Array.PrototypeObject._originalIteratorFunction);
             => ReferenceEquals(Get(GlobalSymbolRegistry.Iterator), _engine.Realm.Intrinsics.Array.PrototypeObject._originalIteratorFunction);
 
 
-        public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
+        /// <summary>
+        /// Checks whether there have been changes to object prototype chain which could render fast access patterns impossible.
+        /// </summary>
+        internal bool CanUseFastAccess
+        {
+            get
+            {
+                if ((_objectChangeFlags & ObjectChangeFlags.NonDefaultDataDescriptorUsage) != 0)
+                {
+                    // could be a mutating property for example, length might change, not safe anymore
+                    return false;
+                }
+                
+                if (_prototype is not ArrayPrototype arrayPrototype 
+                    || !ReferenceEquals(_prototype, _engine.Realm.Intrinsics.Array.PrototypeObject))
+                {
+                    // somebody has switched prototype
+                    return false;
+                }
+
+                if ((arrayPrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) != 0)
+                {
+                    // maybe somebody moved integer property to prototype? not safe anymore
+                    return false;
+                }
+
+                if (arrayPrototype.Prototype is not ObjectPrototype arrayPrototypePrototype 
+                    || !ReferenceEquals(arrayPrototypePrototype, _engine.Realm.Intrinsics.Array.PrototypeObject.Prototype))
+                {
+                    return false;
+                }
+
+                return (arrayPrototypePrototype._objectChangeFlags & ObjectChangeFlags.ArrayIndex) == 0;
+            }
+        }
+
+        public sealed override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
         {
         {
+            var isArrayIndex = IsArrayIndex(property, out var index);
+            TrackChanges(property, desc, isArrayIndex);
+
+            if (isArrayIndex)
+            {
+                return DefineOwnProperty(index, desc);
+            }
+
             if (property == CommonProperties.Length)
             if (property == CommonProperties.Length)
             {
             {
                 var value = desc.Value;
                 var value = desc.Value;
@@ -205,11 +251,6 @@ namespace Jint.Native.Array
                 return true;
                 return true;
             }
             }
 
 
-            if (IsArrayIndex(property, out var index))
-            {
-                return DefineOwnProperty(index, desc);
-            }
-
             return base.DefineOwnProperty(property, desc);
             return base.DefineOwnProperty(property, desc);
         }
         }
 
 
@@ -249,7 +290,7 @@ namespace Jint.Native.Array
             return (uint) ((JsNumber) _length._value)._value;
             return (uint) ((JsNumber) _length._value)._value;
         }
         }
 
 
-        protected override void AddProperty(JsValue property, PropertyDescriptor descriptor)
+        protected sealed override void AddProperty(JsValue property, PropertyDescriptor descriptor)
         {
         {
             if (property == CommonProperties.Length)
             if (property == CommonProperties.Length)
             {
             {
@@ -260,7 +301,7 @@ namespace Jint.Native.Array
             base.AddProperty(property, descriptor);
             base.AddProperty(property, descriptor);
         }
         }
 
 
-        protected override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
+        protected sealed override bool TryGetProperty(JsValue property, out PropertyDescriptor descriptor)
         {
         {
             if (property == CommonProperties.Length)
             if (property == CommonProperties.Length)
             {
             {
@@ -271,7 +312,7 @@ namespace Jint.Native.Array
             return base.TryGetProperty(property, out descriptor);
             return base.TryGetProperty(property, out descriptor);
         }
         }
 
 
-        public override List<JsValue> GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
+        public sealed override List<JsValue> GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
         {
         {
             if ((types & Types.String) == 0)
             if ((types & Types.String) == 0)
             {
             {
@@ -308,7 +349,7 @@ namespace Jint.Native.Array
             return properties;
             return properties;
         }
         }
 
 
-        public override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()
+        public sealed override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()
         {
         {
             if (_dense != null)
             if (_dense != null)
             {
             {
@@ -340,7 +381,7 @@ namespace Jint.Native.Array
             }
             }
         }
         }
 
 
-        public override PropertyDescriptor GetOwnProperty(JsValue property)
+        public sealed override PropertyDescriptor GetOwnProperty(JsValue property)
         {
         {
             if (property == CommonProperties.Length)
             if (property == CommonProperties.Length)
             {
             {
@@ -390,9 +431,37 @@ namespace Jint.Native.Array
             return Prototype?.GetProperty(JsString.Create(index)) ?? PropertyDescriptor.Undefined;
             return Prototype?.GetProperty(JsString.Create(index)) ?? PropertyDescriptor.Undefined;
         }
         }
 
 
-        protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
+        public sealed override bool Set(JsValue property, JsValue value, JsValue receiver)
         {
         {
-            if (IsArrayIndex(property, out var index))
+            if (ReferenceEquals(receiver, this) && Extensible && IsArrayIndex(property, out var index))
+            {
+                if (TryGetDescriptor(index, out var descriptor))
+                {
+                    if (descriptor.IsDefaultArrayValueDescriptor())
+                    {
+                        // fast path with direct write without allocations
+                        descriptor.Value = value;
+                        return true;
+                    }
+                }
+                else if (CanUseFastAccess)
+                {
+                    // we know it's to be written to own array backing field as new value
+                    WriteArrayValue(index, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
+                    EnsureCorrectLength(index);
+                    return true;
+                }
+            }
+
+            // slow path
+            return base.Set(property, value, receiver);
+        }
+
+        protected internal sealed override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
+        {
+            var isArrayIndex = IsArrayIndex(property, out var index);
+            TrackChanges(property, desc, isArrayIndex);
+            if (isArrayIndex)
             {
             {
                 WriteArrayValue(index, desc);
                 WriteArrayValue(index, desc);
             }
             }
@@ -406,7 +475,25 @@ namespace Jint.Native.Array
             }
             }
         }
         }
 
 
-        public override bool HasOwnProperty(JsValue p)
+        private void TrackChanges(JsValue property, PropertyDescriptor desc, bool isArrayIndex)
+        {
+            EnsureInitialized();
+            if (!desc.IsDefaultArrayValueDescriptor())
+            {
+                _objectChangeFlags |= ObjectChangeFlags.NonDefaultDataDescriptorUsage;
+            }
+
+            if (isArrayIndex)
+            {
+                _objectChangeFlags |= ObjectChangeFlags.ArrayIndex;
+            }
+            else
+            {
+                _objectChangeFlags |= property.IsSymbol() ? ObjectChangeFlags.Symbol : ObjectChangeFlags.Property;
+            }
+        }
+
+        public sealed override bool HasOwnProperty(JsValue p)
         {
         {
             if (IsArrayIndex(p, out var index))
             if (IsArrayIndex(p, out var index))
             {
             {
@@ -423,7 +510,7 @@ namespace Jint.Native.Array
             return base.HasOwnProperty(p);
             return base.HasOwnProperty(p);
         }
         }
 
 
-        public override void RemoveOwnProperty(JsValue p)
+        public sealed override void RemoveOwnProperty(JsValue p)
         {
         {
             if (IsArrayIndex(p, out var index))
             if (IsArrayIndex(p, out var index))
             {
             {
@@ -439,7 +526,7 @@ namespace Jint.Native.Array
         }
         }
 
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static bool IsArrayIndex(JsValue p, out uint index)
+        internal static bool IsArrayIndex(JsValue p, out uint index)
         {
         {
             if (p is JsNumber number)
             if (p is JsNumber number)
             {
             {
@@ -516,15 +603,21 @@ namespace Jint.Native.Array
         {
         {
             if (updateLength)
             if (updateLength)
             {
             {
-                var length = GetLength();
-                if (index >= length)
-                {
-                    SetLength(index + 1);
-                }
+                EnsureCorrectLength(index);
             }
             }
             WriteArrayValue(index, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
             WriteArrayValue(index, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
         }
         }
 
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void EnsureCorrectLength(uint index)
+        {
+            var length = GetLength();
+            if (index >= length)
+            {
+                SetLength(index + 1);
+            }
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal void SetLength(uint length)
         internal void SetLength(uint length)
         {
         {
@@ -814,7 +907,7 @@ namespace Jint.Native.Array
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        internal override bool FindWithCallback(
+        internal sealed override bool FindWithCallback(
             JsValue[] arguments,
             JsValue[] arguments,
             out uint index,
             out uint index,
             out JsValue value,
             out JsValue value,
@@ -881,9 +974,9 @@ namespace Jint.Native.Array
             return false;
             return false;
         }
         }
 
 
-        public override uint Length => GetLength();
+        public sealed override uint Length => GetLength();
 
 
-        internal override bool IsIntegerIndexedArray => true;
+        internal sealed override bool IsIntegerIndexedArray => true;
 
 
         public JsValue this[uint index]
         public JsValue this[uint index]
         {
         {
@@ -943,7 +1036,7 @@ namespace Jint.Native.Array
             }
             }
         }
         }
 
 
-        public override string ToString()
+        public sealed override string ToString()
         {
         {
             // debugger can make things hard when evaluates computed values
             // debugger can make things hard when evaluates computed values
             return "(" + (_length?._value.AsNumber() ?? 0) + ")[]";
             return "(" + (_length?._value.AsNumber() ?? 0) + ")[]";
@@ -956,4 +1049,11 @@ namespace Jint.Native.Array
             );
             );
         }
         }
     }
     }
+
+    internal static class ArrayPropertyDescriptorExtensions
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsDefaultArrayValueDescriptor(this PropertyDescriptor propertyDescriptor)
+            => propertyDescriptor.Flags == PropertyFlag.ConfigurableEnumerableWritable && propertyDescriptor.IsDataDescriptor();
+    }
 }
 }

+ 3 - 3
Jint/Native/Array/ArrayOperations.cs

@@ -15,7 +15,7 @@ namespace Jint.Native.Array
 
 
         public static ArrayOperations For(ObjectInstance instance)
         public static ArrayOperations For(ObjectInstance instance)
         {
         {
-            if (instance is ArrayInstance arrayInstance)
+            if (instance is ArrayInstance { CanUseFastAccess: true } arrayInstance)
             {
             {
                 return new ArrayInstanceOperations(arrayInstance);
                 return new ArrayInstanceOperations(arrayInstance);
             }
             }
@@ -294,10 +294,10 @@ namespace Jint.Native.Array
             public override void DeletePropertyOrThrow(ulong index)
             public override void DeletePropertyOrThrow(ulong index)
                 => _target.DeletePropertyOrThrow((uint) index);
                 => _target.DeletePropertyOrThrow((uint) index);
 
 
-            public override void CreateDataPropertyOrThrow(ulong index, JsValue value)
+            public override void CreateDataPropertyOrThrow(ulong index, JsValue value) 
                 => _target.SetIndexValue((uint) index, value, updateLength: false);
                 => _target.SetIndexValue((uint) index, value, updateLength: false);
 
 
-            public override void Set(ulong index, JsValue value, bool updateLength = false, bool throwOnError = true)
+            public override void Set(ulong index, JsValue value, bool updateLength = false, bool throwOnError = true) 
                 => _target.SetIndexValue((uint) index, value, updateLength);
                 => _target.SetIndexValue((uint) index, value, updateLength);
         }
         }
 
 

+ 34 - 27
Jint/Native/Array/ArrayPrototype.cs

@@ -9,6 +9,7 @@ using Jint.Native.Symbol;
 using Jint.Pooling;
 using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors;
+using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
 
 
 namespace Jint.Native.Array
 namespace Jint.Native.Array
@@ -36,25 +37,6 @@ namespace Jint.Native.Array
 
 
         protected override void Initialize()
         protected override void Initialize()
         {
         {
-            var unscopables = new ObjectInstance(_engine)
-            {
-                _prototype = null
-            };
-
-            unscopables.SetDataProperty("at", JsBoolean.True);
-            unscopables.SetDataProperty("copyWithin", JsBoolean.True);
-            unscopables.SetDataProperty("entries", JsBoolean.True);
-            unscopables.SetDataProperty("fill", JsBoolean.True);
-            unscopables.SetDataProperty("find", JsBoolean.True);
-            unscopables.SetDataProperty("findIndex", JsBoolean.True);
-            unscopables.SetDataProperty("findLast", JsBoolean.True);
-            unscopables.SetDataProperty("findLastIndex", JsBoolean.True);
-            unscopables.SetDataProperty("flat", JsBoolean.True);
-            unscopables.SetDataProperty("flatMap", JsBoolean.True);
-            unscopables.SetDataProperty("includes", JsBoolean.True);
-            unscopables.SetDataProperty("keys", JsBoolean.True);
-            unscopables.SetDataProperty("values", JsBoolean.True);
-
             const PropertyFlag propertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable;
             const PropertyFlag propertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable;
             var properties = new PropertyDictionary(34, checkExistingKeys: false)
             var properties = new PropertyDictionary(34, checkExistingKeys: false)
             {
             {
@@ -100,7 +82,31 @@ namespace Jint.Native.Array
             var symbols = new SymbolDictionary(2)
             var symbols = new SymbolDictionary(2)
             {
             {
                 [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(_originalIteratorFunction, propertyFlags),
                 [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(_originalIteratorFunction, propertyFlags),
-                [GlobalSymbolRegistry.Unscopables] = new PropertyDescriptor(unscopables, PropertyFlag.Configurable)
+                [GlobalSymbolRegistry.Unscopables] = new LazyPropertyDescriptor(_engine, static state =>
+                {            
+                    var unscopables = new ObjectInstance((Engine) state)
+                    {
+                        _prototype = null
+                    };
+
+                    unscopables.SetDataProperty("at", JsBoolean.True);
+                    unscopables.SetDataProperty("copyWithin", JsBoolean.True);
+                    unscopables.SetDataProperty("entries", JsBoolean.True);
+                    unscopables.SetDataProperty("fill", JsBoolean.True);
+                    unscopables.SetDataProperty("find", JsBoolean.True);
+                    unscopables.SetDataProperty("findIndex", JsBoolean.True);
+                    unscopables.SetDataProperty("findLast", JsBoolean.True);
+                    unscopables.SetDataProperty("findLastIndex", JsBoolean.True);
+                    unscopables.SetDataProperty("flat", JsBoolean.True);
+                    unscopables.SetDataProperty("flatMap", JsBoolean.True);
+                    unscopables.SetDataProperty("groupBy", JsBoolean.True);
+                    unscopables.SetDataProperty("groupByToMap", JsBoolean.True);
+                    unscopables.SetDataProperty("includes", JsBoolean.True);
+                    unscopables.SetDataProperty("keys", JsBoolean.True);
+                    unscopables.SetDataProperty("values", JsBoolean.True);
+
+                    return unscopables;
+                }, PropertyFlag.Configurable)
             };
             };
             SetSymbols(symbols);
             SetSymbols(symbols);
         }
         }
@@ -953,6 +959,9 @@ namespace Jint.Native.Array
             return a.Target;
             return a.Target;
         }
         }
 
 
+        /// <summary>
+        /// /https://tc39.es/ecma262/#sec-array.prototype.unshift
+        /// </summary>
         private JsValue Unshift(JsValue thisObj, JsValue[] arguments)
         private JsValue Unshift(JsValue thisObj, JsValue[] arguments)
         {
         {
             var o = ArrayOperations.For(_realm, thisObj);
             var o = ArrayOperations.For(_realm, thisObj);
@@ -972,7 +981,7 @@ namespace Jint.Native.Array
                 var to = k + argCount - 1;
                 var to = k + argCount - 1;
                 if (o.TryGetValue(from, out var fromValue))
                 if (o.TryGetValue(from, out var fromValue))
                 {
                 {
-                    o.Set(to, fromValue, true);
+                    o.Set(to, fromValue, updateLength: false);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -982,7 +991,7 @@ namespace Jint.Native.Array
 
 
             for (uint j = 0; j < argCount; j++)
             for (uint j = 0; j < argCount; j++)
             {
             {
-                o.Set(j, arguments[j], true);
+                o.Set(j, arguments[j], updateLength: false);
             }
             }
 
 
             o.SetLength(len + argCount);
             o.SetLength(len + argCount);
@@ -1035,10 +1044,8 @@ namespace Jint.Native.Array
                 uint j;
                 uint j;
                 for (j = 0; j < itemCount; ++j)
                 for (j = 0; j < itemCount; ++j)
                 {
                 {
-                    objectInstance.Set(j, array[j], throwOnError: true);
-                    // TODO if we could keep track of data descriptors and whether prototype chain is unchanged
-                    // we could do faster direct write
-                    // obj.Set(j, array[j], updateLength: true, throwOnError: true);
+                    var updateLength = j == itemCount - 1;
+                    obj.Set(j, array[j], updateLength: updateLength, throwOnError: true);
                 }
                 }
                 for (; j < len; ++j)
                 for (; j < len; ++j)
                 {
                 {
@@ -1418,7 +1425,7 @@ namespace Jint.Native.Array
         /// </summary>
         /// </summary>
         public JsValue Push(JsValue thisObject, JsValue[] arguments)
         public JsValue Push(JsValue thisObject, JsValue[] arguments)
         {
         {
-            if (thisObject is ArrayInstance arrayInstance)
+            if (thisObject is ArrayInstance arrayInstance && arrayInstance.CanUseFastAccess)
             {
             {
                 return arrayInstance.Push(arguments);
                 return arrayInstance.Push(arguments);
             }
             }

+ 3 - 4
Jint/Native/ArrayBuffer/ArrayBufferInstance.cs

@@ -61,10 +61,9 @@ namespace Jint.Native.ArrayBuffer
         internal ArrayBufferInstance CloneArrayBuffer(
         internal ArrayBufferInstance CloneArrayBuffer(
             ArrayBufferConstructor constructor,
             ArrayBufferConstructor constructor,
             int srcByteOffset,
             int srcByteOffset,
-            uint srcLength,
-            JsValue cloneConstructor)
+            uint srcLength)
         {
         {
-            var targetBuffer = constructor.AllocateArrayBuffer(cloneConstructor, srcLength);
+            var targetBuffer = constructor.AllocateArrayBuffer(_engine.Realm.Intrinsics.ArrayBuffer, srcLength);
             AssertNotDetached();
             AssertNotDetached();
             var srcBlock = _arrayBufferData;
             var srcBlock = _arrayBufferData;
             var targetBlock = targetBuffer.ArrayBufferData;
             var targetBlock = targetBuffer.ArrayBufferData;
@@ -315,4 +314,4 @@ namespace Jint.Native.ArrayBuffer
             }
             }
         }
         }
     }
     }
-}
+}

+ 16 - 0
Jint/Native/Object/ObjectChangeFlags.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace Jint.Native.Object;
+
+/// <summary>
+/// Keeps track of changes to object, mainly meant for prototype change detection.
+/// </summary>
+[Flags]
+internal enum ObjectChangeFlags
+{
+    None = 0,
+    Property = 1,
+    Symbol = 2,
+    ArrayIndex = 4,
+    NonDefaultDataDescriptorUsage = 8
+}

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

@@ -10,7 +10,7 @@ namespace Jint.Native.Object
 {
 {
     public sealed class ObjectConstructor : FunctionInstance, IConstructor
     public sealed class ObjectConstructor : FunctionInstance, IConstructor
     {
     {
-        private static readonly JsString _name = new JsString("delegate");
+        private static readonly JsString _name = new JsString("Object");
 
 
         internal ObjectConstructor(
         internal ObjectConstructor(
             Engine engine,
             Engine engine,

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

@@ -1218,15 +1218,16 @@ namespace Jint.Native.Object
         /// <summary>
         /// <summary>
         /// https://tc39.es/ecma262/#sec-createdatapropertyorthrow
         /// https://tc39.es/ecma262/#sec-createdatapropertyorthrow
         /// </summary>
         /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal bool CreateDataProperty(JsValue p, JsValue v)
         internal bool CreateDataProperty(JsValue p, JsValue v)
         {
         {
-            var newDesc = new PropertyDescriptor(v, PropertyFlag.ConfigurableEnumerableWritable);
-            return DefineOwnProperty(p, newDesc);
+            return DefineOwnProperty(p, new PropertyDescriptor(v, PropertyFlag.ConfigurableEnumerableWritable));
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// https://tc39.es/ecma262/#sec-createdataproperty
         /// https://tc39.es/ecma262/#sec-createdataproperty
         /// </summary>
         /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal bool CreateDataPropertyOrThrow(JsValue p, JsValue v)
         internal bool CreateDataPropertyOrThrow(JsValue p, JsValue v)
         {
         {
             if (!CreateDataProperty(p, v))
             if (!CreateDataProperty(p, v))

+ 47 - 0
Jint/Native/Object/ObjectPrototype.cs

@@ -1,4 +1,5 @@
 using Jint.Collections;
 using Jint.Collections;
+using Jint.Native.Array;
 using Jint.Native.Proxy;
 using Jint.Native.Proxy;
 using Jint.Native.Symbol;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime;
@@ -10,6 +11,7 @@ namespace Jint.Native.Object
     public sealed class ObjectPrototype : Prototype
     public sealed class ObjectPrototype : Prototype
     {
     {
         private readonly ObjectConstructor _constructor;
         private readonly ObjectConstructor _constructor;
+        internal ObjectChangeFlags _objectChangeFlags;
 
 
         internal ObjectPrototype(
         internal ObjectPrototype(
             Engine engine,
             Engine engine,
@@ -26,6 +28,26 @@ namespace Jint.Native.Object
             var properties = new PropertyDictionary(8, checkExistingKeys: false)
             var properties = new PropertyDictionary(8, checkExistingKeys: false)
             {
             {
                 ["constructor"] = new PropertyDescriptor(_constructor, propertyFlags),
                 ["constructor"] = new PropertyDescriptor(_constructor, propertyFlags),
+                ["__proto__"] = new GetSetPropertyDescriptor(
+                    new ClrFunctionInstance(Engine, "get __proto__", (thisObject, _) => TypeConverter.ToObject(_realm, thisObject).GetPrototypeOf() ?? Null, 0, lengthFlags),
+                    new ClrFunctionInstance(Engine, "set __proto__", (thisObject, arguments) =>
+                    {
+                        TypeConverter.CheckObjectCoercible(_engine, thisObject);
+                        
+                        var proto = arguments.At(0);
+                        if (!proto.IsObject() && !proto.IsNull() || thisObject is not ObjectInstance objectInstance)
+                        {
+                            return Undefined;
+                        }
+
+                        if (!objectInstance.SetPrototypeOf(proto))
+                        {
+                            ExceptionHelper.ThrowTypeError(_realm, "Invalid prototype");
+                        }
+                        
+                        return Undefined;
+                    }, 0, lengthFlags),
+                    enumerable: false, configurable: true),
                 ["toString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toString", ToObjectString, 0, lengthFlags), propertyFlags),
                 ["toString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toString", ToObjectString, 0, lengthFlags), propertyFlags),
                 ["toLocaleString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, lengthFlags), propertyFlags),
                 ["toLocaleString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, lengthFlags), propertyFlags),
                 ["valueOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, lengthFlags), propertyFlags),
                 ["valueOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, lengthFlags), propertyFlags),
@@ -35,6 +57,31 @@ namespace Jint.Native.Object
             };
             };
             SetProperties(properties);
             SetProperties(properties);
         }
         }
+        
+        public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
+        {
+            TrackChanges(property);
+            return base.DefineOwnProperty(property, desc);
+        }
+
+        protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
+        {
+            TrackChanges(property);
+            base.SetOwnProperty(property, desc);
+        }
+
+        private void TrackChanges(JsValue property)
+        {
+            EnsureInitialized();
+            if (ArrayInstance.IsArrayIndex(property, out _))
+            {
+                _objectChangeFlags |= ObjectChangeFlags.ArrayIndex;
+            }
+            else
+            {
+                _objectChangeFlags |= property.IsSymbol() ? ObjectChangeFlags.Symbol : ObjectChangeFlags.Property;
+            }
+        }
 
 
         private JsValue PropertyIsEnumerable(JsValue thisObject, JsValue[] arguments)
         private JsValue PropertyIsEnumerable(JsValue thisObject, JsValue[] arguments)
         {
         {

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

@@ -607,7 +607,7 @@ namespace Jint.Native.String
             var s = TypeConverter.ToString(thisObj);
             var s = TypeConverter.ToString(thisObj);
             var that = TypeConverter.ToString(arguments.At(0));
             var that = TypeConverter.ToString(arguments.At(0));
 
 
-            return string.CompareOrdinal(s, that);
+            return string.CompareOrdinal(s.Normalize(NormalizationForm.FormKD), that.Normalize(NormalizationForm.FormKD));
         }
         }
 
 
         private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments)
         private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments)

+ 8 - 27
Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs

@@ -872,7 +872,7 @@ namespace Jint.Native.TypedArray
             }
             }
             else
             else
             {
             {
-                SetTypedArrayFromArrayLike(target, targetOffset, source);
+                SetTypedArrayFromArrayLike(target, (int) targetOffset, source);
             }
             }
 
 
             return Undefined;
             return Undefined;
@@ -930,7 +930,7 @@ namespace Jint.Native.TypedArray
             if (same)
             if (same)
             {
             {
                 var srcByteLength = source._byteLength;
                 var srcByteLength = source._byteLength;
-                srcBuffer = srcBuffer.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, srcByteLength, _realm.Intrinsics.ArrayBuffer);
+                srcBuffer = srcBuffer.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, srcByteLength);
                 // %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects.
                 // %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects.
                 srcByteIndex = 0;
                 srcByteIndex = 0;
             }
             }
@@ -968,15 +968,12 @@ namespace Jint.Native.TypedArray
         /// <summary>
         /// <summary>
         /// https://tc39.es/ecma262/#sec-settypedarrayfromarraylike
         /// https://tc39.es/ecma262/#sec-settypedarrayfromarraylike
         /// </summary>
         /// </summary>
-        private void SetTypedArrayFromArrayLike(TypedArrayInstance target, double targetOffset, JsValue source)
+        private void SetTypedArrayFromArrayLike(TypedArrayInstance target, int targetOffset, JsValue source)
         {
         {
             var targetBuffer = target._viewedArrayBuffer;
             var targetBuffer = target._viewedArrayBuffer;
             targetBuffer.AssertNotDetached();
             targetBuffer.AssertNotDetached();
 
 
             var targetLength = target._arrayLength;
             var targetLength = target._arrayLength;
-            var targetElementSize = target._arrayElementType.GetElementSize();
-            var targetType = target._arrayElementType;
-            var targetByteOffset = target._byteOffset;
             var src = ArrayOperations.For(TypeConverter.ToObject(_realm, source));
             var src = ArrayOperations.For(TypeConverter.ToObject(_realm, source));
             var srcLength = src.GetLength();
             var srcLength = src.GetLength();
 
 
@@ -990,27 +987,12 @@ namespace Jint.Native.TypedArray
                 ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset");
                 ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset");
             }
             }
 
 
-            var targetByteIndex = targetOffset * targetElementSize + targetByteOffset;
-            ulong k = 0;
-            var limit = targetByteIndex + targetElementSize * srcLength;
-
-            while (targetByteIndex < limit)
+            var k = 0;
+            while (k < srcLength)
             {
             {
-                if (target._contentType == TypedArrayContentType.BigInt)
-                {
-                    var value = src.Get(k).ToBigInteger(_engine);
-                    targetBuffer.AssertNotDetached();
-                    targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered);
-                }
-                else
-                {
-                    var value = TypeConverter.ToNumber(src.Get(k));
-                    targetBuffer.AssertNotDetached();
-                    targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered);
-                }
-
+                var jsValue = src.Get((ulong) k);
+                target.IntegerIndexedElementSet(targetOffset + k, jsValue);
                 k++;
                 k++;
-                targetByteIndex += targetElementSize;
             }
             }
         }
         }
 
 
@@ -1372,7 +1354,6 @@ namespace Jint.Native.TypedArray
                     _comparableArray[1] = y;
                     _comparableArray[1] = y;
 
 
                     var v = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray));
                     var v = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray));
-                    _buffer.AssertNotDetached();
 
 
                     if (double.IsNaN(v))
                     if (double.IsNaN(v))
                     {
                     {
@@ -1431,4 +1412,4 @@ namespace Jint.Native.TypedArray
             }
             }
         }
         }
     }
     }
-}
+}

+ 3 - 6
Jint/Native/TypedArray/TypedArrayConstructor.cs

@@ -147,18 +147,15 @@ namespace Jint.Native.TypedArray
             var elementSize = elementType.GetElementSize();
             var elementSize = elementType.GetElementSize();
             var byteLength = elementSize * elementLength;
             var byteLength = elementSize * elementLength;
 
 
-            var bufferConstructor = (JsValue) (!srcData.IsSharedArrayBuffer
-                ? SpeciesConstructor(srcData, _realm.Intrinsics.ArrayBuffer)
-                : _realm.Intrinsics.ArrayBuffer);
-
+            var arrayBuffer = _realm.Intrinsics.ArrayBuffer;
             ArrayBufferInstance data;
             ArrayBufferInstance data;
             if (elementType == srcType)
             if (elementType == srcType)
             {
             {
-                data = srcData.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, byteLength, bufferConstructor);
+                data = srcData.CloneArrayBuffer(arrayBuffer, srcByteOffset, byteLength);
             }
             }
             else
             else
             {
             {
-                data = _realm.Intrinsics.ArrayBuffer.AllocateArrayBuffer(bufferConstructor, byteLength);
+                data = arrayBuffer.AllocateArrayBuffer(arrayBuffer, byteLength);
                 srcData.AssertNotDetached();
                 srcData.AssertNotDetached();
                 if (srcArray._contentType != o._contentType)
                 if (srcArray._contentType != o._contentType)
                 {
                 {

+ 8 - 16
Jint/Native/TypedArray/TypedArrayInstance.cs

@@ -275,23 +275,15 @@ namespace Jint.Native.TypedArray
 
 
         // helper tot prevent floating point
         // helper tot prevent floating point
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void IntegerIndexedElementSet(int index, JsValue value)
+        internal void IntegerIndexedElementSet(int index, JsValue value)
         {
         {
-            if (_contentType != TypedArrayContentType.BigInt)
-            {
-                var numValue = TypeConverter.ToNumber(value);
-                if (IsValidIntegerIndex(index))
-                {
-                    DoIntegerIndexedElementSet(index, numValue);
-                }
-            }
-            else
+            TypedArrayValue numValue = _contentType != TypedArrayContentType.BigInt 
+                ? TypeConverter.ToNumber(value) 
+                : value.ToBigInteger(_engine);
+
+            if (IsValidIntegerIndex(index))
             {
             {
-                var numValue = value.ToBigInteger(_engine);
-                if (IsValidIntegerIndex(index))
-                {
-                    DoIntegerIndexedElementSet(index, numValue);
-                }
+                DoIntegerIndexedElementSet(index, numValue);
             }
             }
         }
         }
 
 
@@ -391,4 +383,4 @@ namespace Jint.Native.TypedArray
             return array;
             return array;
         }
         }
     }
     }
-}
+}

+ 11 - 1
Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs

@@ -87,6 +87,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                     if (!p.Computed && p.Key is Identifier identifier)
                     if (!p.Computed && p.Key is Identifier identifier)
                     {
                     {
                         propName = identifier.Name;
                         propName = identifier.Name;
+                        _canBuildFast &= propName != "__proto__";
                     }
                     }
 
 
                     _properties[i] = new ObjectProperty(propName, p);
                     _properties[i] = new ObjectProperty(propName, p);
@@ -204,6 +205,15 @@ namespace Jint.Runtime.Interpreter.Expressions
                         closure.SetFunctionName(propName);
                         closure.SetFunctionName(propName);
                     }
                     }
 
 
+                    if (objectProperty._key == "__proto__")
+                    {
+                        if (propValue.IsObject() || propValue.IsNull())
+                        {
+                            obj.SetPrototypeOf(propValue);
+                            continue;
+                        }
+                    }
+                    
                     var propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable);
                     var propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable);
                     obj.DefinePropertyOrThrow(propName, propDesc);
                     obj.DefinePropertyOrThrow(propName, propDesc);
                 }
                 }
@@ -231,4 +241,4 @@ namespace Jint.Runtime.Interpreter.Expressions
             return NormalCompletion(obj);
             return NormalCompletion(obj);
         }
         }
     }
     }
-}
+}