From 53ab42a7c8f38311f4d3ff87aa9f3fbb4b74547b Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 6 Jan 2024 18:11:03 +0200 Subject: [PATCH] Add custom strategy for array read access (#1729) --- Jint/Native/Array/ArrayConstructor.cs | 6 +- Jint/Native/Array/ArrayIteratorPrototype.cs | 2 +- Jint/Native/Array/ArrayOperations.cs | 61 ++++++++++++++++++- Jint/Native/Array/ArrayPrototype.cs | 61 +++++++++---------- Jint/Native/Function/FunctionPrototype.cs | 2 +- Jint/Native/String/StringConstructor.cs | 5 +- .../IntrinsicTypedArrayPrototype.cs | 2 +- .../TypedArray/TypedArrayConstructor.cs | 2 +- .../BindingPatternAssignmentExpression.cs | 2 +- 9 files changed, 97 insertions(+), 46 deletions(-) diff --git a/Jint/Native/Array/ArrayConstructor.cs b/Jint/Native/Array/ArrayConstructor.cs index 920ee82639..6e83b56e88 100644 --- a/Jint/Native/Array/ArrayConstructor.cs +++ b/Jint/Native/Array/ArrayConstructor.cs @@ -114,7 +114,7 @@ private ObjectInstance ConstructArrayFromArrayLike( ? _engine._jsValueArrayPool.RentArray(2) : null; - var target = ArrayOperations.For(a); + var target = ArrayOperations.For(a, forWrite: true); uint n = 0; for (uint i = 0; i < length; i++) { @@ -157,7 +157,7 @@ public ArrayProtocol( ICallable? callable) : base(engine, iterator, 2) { _thisArg = thisArg; - _instance = ArrayOperations.For(instance); + _instance = ArrayOperations.For(instance, forWrite: true); _callable = callable; } @@ -310,7 +310,7 @@ private JsArray Construct(JsValue[] arguments, ulong capacity, ObjectInstance pr break; case JsArray array: // direct copy - instance = (JsArray) ConstructArrayFromArrayLike(Undefined, ArrayOperations.For(array), callable: null, this); + instance = (JsArray) ConstructArrayFromArrayLike(Undefined, ArrayOperations.For(array, forWrite: false), callable: null, this); break; default: instance = ArrayCreate(capacity, prototypeObject); diff --git a/Jint/Native/Array/ArrayIteratorPrototype.cs b/Jint/Native/Array/ArrayIteratorPrototype.cs index f65e355d7f..4dc1e38a73 100644 --- a/Jint/Native/Array/ArrayIteratorPrototype.cs +++ b/Jint/Native/Array/ArrayIteratorPrototype.cs @@ -97,7 +97,7 @@ public ArrayLikeIterator(Engine engine, ObjectInstance objectInstance, ArrayIter _typedArray = objectInstance as JsTypedArray; if (_typedArray is null) { - _operations = ArrayOperations.For(objectInstance); + _operations = ArrayOperations.For(objectInstance, forWrite: false); } _position = 0; diff --git a/Jint/Native/Array/ArrayOperations.cs b/Jint/Native/Array/ArrayOperations.cs index 8b8d626d44..ef22bc239d 100644 --- a/Jint/Native/Array/ArrayOperations.cs +++ b/Jint/Native/Array/ArrayOperations.cs @@ -25,12 +25,17 @@ public static ArrayOperations For(Realm realm, JsValue value, bool forWrite) { return new JsStringOperations(realm, stringInstance); } + + if (value is JsArray { CanUseFastAccess: true } array && array.Length <= (array._dense?.Length ?? -1)) + { + return new ArrayReadOperations(array); + } } - return For(TypeConverter.ToObject(realm, value)); + return For(TypeConverter.ToObject(realm, value), forWrite); } - public static ArrayOperations For(ObjectInstance instance) + public static ArrayOperations For(ObjectInstance instance, bool forWrite) { if (instance is JsArray { CanUseFastAccess: true } arrayInstance) { @@ -352,7 +357,7 @@ public override void DeletePropertyOrThrow(ulong index) public override bool HasProperty(ulong index) => _target.HasProperty(index); } - private sealed class JsStringOperations : ArrayOperations + private sealed class JsStringOperations : ArrayOperations { private readonly Realm _realm; private readonly JsString _target; @@ -405,6 +410,56 @@ public override bool TryGetValue(ulong index, out JsValue value) public override void DeletePropertyOrThrow(ulong index) => throw new NotSupportedException(); } + + private sealed class ArrayReadOperations : ArrayOperations + { + private readonly JsArray _target; + private readonly JsValue?[] _data; + private readonly uint _length; + + public ArrayReadOperations(JsArray target) + { + _target = target; + _data = target._dense ?? System.Array.Empty(); + _length = target.Length; + } + + public override ObjectInstance Target => _target; + + public override ulong GetSmallestIndex(ulong length) => 0; + + public override uint GetLength() => _length; + + public override ulong GetLongLength() => _length; + + public override void SetLength(ulong length) => throw new NotSupportedException(); + + public override void EnsureCapacity(ulong capacity) + { + } + + public override JsValue Get(ulong index) => (index < (ulong) _data.Length ? _data[(int) index] : JsValue.Undefined) ?? JsValue.Undefined; + + public override bool TryGetValue(ulong index, out JsValue value) + { + if (index < _length) + { + value = _data[(int) index]!; + return value is not null; + } + + value = JsValue.Undefined; + return false; + } + + public override bool HasProperty(ulong index) => index < _length && _data[index] is not null; + + 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(); + } } /// diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index d23cb30d99..527e908514 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -141,7 +141,7 @@ internal ObjectInstance Values(JsValue thisObject, JsValue[] arguments) private ObjectInstance With(JsValue thisObject, JsValue[] arguments) { - var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject)); + var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject), forWrite: false); var len = o.GetLongLength(); var relativeIndex = TypeConverter.ToIntegerOrInfinity(arguments.At(0)); var value = arguments.At(1); @@ -193,7 +193,7 @@ private JsValue Fill(JsValue thisObject, JsValue[] arguments) var o = TypeConverter.ToObject(_realm, thisObject); - var operations = ArrayOperations.For(o); + var operations = ArrayOperations.For(o, forWrite: true); var length = operations.GetLongLength(); var relativeStart = TypeConverter.ToIntegerOrInfinity(start); @@ -246,7 +246,7 @@ private JsValue CopyWithin(JsValue thisObject, JsValue[] arguments) JsValue start = arguments.At(1); JsValue end = arguments.At(2); - var operations = ArrayOperations.For(o); + var operations = ArrayOperations.For(o, forWrite: true); var len = operations.GetLongLength(); var relativeTarget = TypeConverter.ToIntegerOrInfinity(target); @@ -379,7 +379,7 @@ private JsValue Reduce(JsValue thisObject, JsValue[] arguments) var callbackfn = arguments.At(0); var initialValue = arguments.At(1); - var o = ArrayOperations.For(_realm, thisObject, forWrite: false); + var o = ArrayOperations.For(_realm, thisObject, forWrite: true); var len = o.GetLength(); var callable = GetCallable(callbackfn); @@ -447,7 +447,7 @@ private JsValue Filter(JsValue thisObject, JsValue[] arguments) var callable = GetCallable(callbackfn); var a = _realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), 0); - var operations = ArrayOperations.For(a); + var operations = ArrayOperations.For(a, forWrite: true); uint to = 0; var args = _engine._jsValueArrayPool.RentArray(3); @@ -496,7 +496,7 @@ private JsValue Map(JsValue thisObject, JsValue[] arguments) var thisArg = arguments.At(1); var callable = GetCallable(callbackfn); - var a = ArrayOperations.For(_realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), (uint) len)); + var a = ArrayOperations.For(_realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), (uint) len), forWrite: true); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o.Target; for (uint k = 0; k < len; k++) @@ -518,8 +518,7 @@ private JsValue Map(JsValue thisObject, JsValue[] arguments) /// private JsValue Flat(JsValue thisObject, JsValue[] arguments) { - var O = TypeConverter.ToObject(_realm, thisObject); - var operations = ArrayOperations.For(O); + var operations = ArrayOperations.For(_realm, thisObject, forWrite: false); var sourceLen = operations.GetLength(); double depthNum = 1; var depth = arguments.At(0); @@ -533,8 +532,8 @@ private JsValue Flat(JsValue thisObject, JsValue[] arguments) depthNum = 0; } - var A = _realm.Intrinsics.Array.ArraySpeciesCreate(O, 0); - FlattenIntoArray(A, O, sourceLen, 0, depthNum); + var A = _realm.Intrinsics.Array.ArraySpeciesCreate(operations.Target, 0); + FlattenIntoArray(A, operations, sourceLen, 0, depthNum); return A; } @@ -543,7 +542,7 @@ private JsValue Flat(JsValue thisObject, JsValue[] arguments) /// private JsValue FlatMap(JsValue thisObject, JsValue[] arguments) { - var O = TypeConverter.ToObject(_realm, thisObject); + var O = ArrayOperations.For(_realm, thisObject, forWrite: false); var mapperFunction = arguments.At(0); var thisArg = arguments.At(1); @@ -554,7 +553,7 @@ private JsValue FlatMap(JsValue thisObject, JsValue[] arguments) ExceptionHelper.ThrowTypeError(_realm, "flatMap mapper function is not callable"); } - var A = _realm.Intrinsics.Array.ArraySpeciesCreate(O, 0); + var A = _realm.Intrinsics.Array.ArraySpeciesCreate(O.Target, 0); FlattenIntoArray(A, O, sourceLen, 0, 1, (ICallable) mapperFunction, thisArg); return A; } @@ -562,32 +561,31 @@ private JsValue FlatMap(JsValue thisObject, JsValue[] arguments) /// /// https://tc39.es/ecma262/#sec-flattenintoarray /// - private long FlattenIntoArray( + private ulong FlattenIntoArray( ObjectInstance target, - ObjectInstance source, + ArrayOperations source, uint sourceLen, - long start, + ulong start, double depth, ICallable? mapperFunction = null, JsValue? thisArg = null) { var targetIndex = start; - var sourceIndex = 0; + ulong sourceIndex = 0; var callArguments = System.Array.Empty(); if (mapperFunction is not null) { callArguments = _engine._jsValueArrayPool.RentArray(3); - callArguments[2] = source; + callArguments[2] = source.Target; } while (sourceIndex < sourceLen) { - var P = TypeConverter.ToString(sourceIndex); - var exists = source.HasProperty(P); + var exists = source.HasProperty(sourceIndex); if (exists) { - var element = source.Get(P); + var element = source.Get(sourceIndex); if (mapperFunction is not null) { callArguments[0] = element; @@ -609,7 +607,7 @@ private long FlattenIntoArray( var objectInstance = (ObjectInstance) element; var elementLen = objectInstance.GetLength(); - targetIndex = FlattenIntoArray(target, objectInstance, elementLen, targetIndex, newDepth); + targetIndex = FlattenIntoArray(target, ArrayOperations.For(objectInstance, forWrite: false), elementLen, targetIndex, newDepth); } else { @@ -896,7 +894,7 @@ private JsValue Splice(JsValue thisObject, JsValue[] arguments) var deleteCount = arguments.At(1); var obj = TypeConverter.ToObject(_realm, thisObject); - var o = ArrayOperations.For(_realm, obj, forWrite: false); + var o = ArrayOperations.For(_realm, obj, forWrite: true); var len = o.GetLongLength(); var relativeStart = TypeConverter.ToInteger(start); @@ -943,7 +941,7 @@ private JsValue Splice(JsValue thisObject, JsValue[] arguments) } var instance = _realm.Intrinsics.Array.ArraySpeciesCreate(obj, actualDeleteCount); - var a = ArrayOperations.For(instance); + var a = ArrayOperations.For(instance, forWrite: true); for (uint k = 0; k < actualDeleteCount; k++) { var index = actualStart + k; @@ -1051,8 +1049,7 @@ private JsValue Unshift(JsValue thisObject, JsValue[] arguments) /// private JsValue Sort(JsValue thisObject, JsValue[] arguments) { - var objectInstance = TypeConverter.ToObject(_realm, thisObject); - var obj = ArrayOperations.For(objectInstance); + var obj = ArrayOperations.For(_realm, thisObject, forWrite: true); var compareFn = GetCompareFunction(arguments.At(0)); var len = obj.GetLength(); @@ -1150,7 +1147,7 @@ private JsValue Slice(JsValue thisObject, JsValue[] arguments) else { // slower path - var operations = ArrayOperations.For(a); + var operations = ArrayOperations.For(a, forWrite: true); for (uint n = 0; k < final; k++, n++) { if (o.TryGetValue(k, out var kValue)) @@ -1328,7 +1325,7 @@ private JsValue Concat(JsValue thisObject, JsValue[] arguments) uint n = 0; var a = _realm.Intrinsics.Array.ArraySpeciesCreate(TypeConverter.ToObject(_realm, thisObject), 0); - var aOperations = ArrayOperations.For(a); + var aOperations = ArrayOperations.For(a, forWrite: true); for (var i = 0; i < items.Count; i++) { var e = items[i]; @@ -1341,7 +1338,7 @@ private JsValue Concat(JsValue thisObject, JsValue[] arguments) } else { - var operations = ArrayOperations.For(oi); + var operations = ArrayOperations.For(oi, forWrite: false); var len = operations.GetLongLength(); if (n + len > ArrayOperations.MaxArrayLikeLength) @@ -1390,7 +1387,7 @@ internal JsValue ToString(JsValue thisObject, JsValue[] arguments) private JsValue ToReversed(JsValue thisObject, JsValue[] arguments) { - var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject)); + var o = ArrayOperations.For(_realm, thisObject, forWrite: false); var len = o.GetLongLength(); @@ -1411,7 +1408,7 @@ private JsValue ToReversed(JsValue thisObject, JsValue[] arguments) private JsValue ToSorted(JsValue thisObject, JsValue[] arguments) { - var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject)); + var o = ArrayOperations.For(_realm, thisObject, forWrite: false); var compareFn = GetCompareFunction(arguments.At(0)); var len = o.GetLongLength(); @@ -1554,7 +1551,7 @@ private JsValue ReduceRight(JsValue thisObject, JsValue[] arguments) var callbackfn = arguments.At(0); var initialValue = arguments.At(1); - var o = ArrayOperations.For(_realm, thisObject, forWrite: false); + var o = ArrayOperations.For(_realm, thisObject, forWrite: true); var len = o.GetLongLength(); var callable = GetCallable(callbackfn); @@ -1615,7 +1612,7 @@ public JsValue Push(JsValue thisObject, JsValue[] arguments) return arrayInstance.Push(arguments); } - var o = ArrayOperations.For(TypeConverter.ToObject(_realm, thisObject)); + var o = ArrayOperations.For(_realm, thisObject, forWrite: true); var n = o.GetLongLength(); if (n + (ulong) arguments.Length > ArrayOperations.MaxArrayLikeLength) diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 37c86296c4..9255a786b5 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -175,7 +175,7 @@ internal static JsValue[] CreateListFromArrayLike(Realm realm, JsValue argArray, { ExceptionHelper.ThrowTypeError(realm); } - var operations = ArrayOperations.For(argArrayObj); + var operations = ArrayOperations.For(argArrayObj, forWrite: false); var argList = elementTypes is null ? operations.GetAll() : operations.GetAll(elementTypes.Value); return argList; } diff --git a/Jint/Native/String/StringConstructor.cs b/Jint/Native/String/StringConstructor.cs index b693a5f076..cb5dfc780e 100644 --- a/Jint/Native/String/StringConstructor.cs +++ b/Jint/Native/String/StringConstructor.cs @@ -134,9 +134,8 @@ private JsValue FromCodePoint(JsValue thisObject, JsValue[] arguments) private JsValue Raw(JsValue thisObject, JsValue[] arguments) { var cooked = TypeConverter.ToObject(_realm, arguments.At(0)); - var raw = TypeConverter.ToObject(_realm, cooked.Get(JintTaggedTemplateExpression.PropertyRaw)); - - var operations = ArrayOperations.For(raw); + var raw = cooked.Get(JintTaggedTemplateExpression.PropertyRaw); + var operations = ArrayOperations.For(_realm, raw, forWrite: false); var length = operations.GetLength(); if (length <= 0) diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs index 7b85bf1ce2..f24d48b1a4 100644 --- a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -1552,7 +1552,7 @@ private JsTypedArray TypedArrayCreateSameType(JsTypedArray exemplar, JsValue[] a private static JsValue[] SortArray(JsArrayBuffer buffer, ICallable? compareFn, JsTypedArray obj) { var comparer = TypedArrayComparer.WithFunction(buffer, compareFn); - var operations = ArrayOperations.For(obj); + var operations = ArrayOperations.For(obj, forWrite: false); try { return operations.OrderBy(x => x, comparer).ToArray(); diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs index 09ab267443..0a0eafbd5b 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -263,7 +263,7 @@ private static void InitializeTypedArrayFromList(JsTypedArray o, List v /// private static void InitializeTypedArrayFromArrayLike(JsTypedArray o, ObjectInstance arrayLike) { - var operations = ArrayOperations.For(arrayLike); + var operations = ArrayOperations.For(arrayLike, forWrite: false); var len = operations.GetLongLength(); o.AllocateTypedArrayBuffer(len); for (uint k = 0; k < len; ++k) diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index 012d9fe0fa..82bbb939b8 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -94,7 +94,7 @@ private static JsValue HandleArrayPattern( // optimize for array unless someone has touched the iterator if (obj.IsArrayLike && obj.HasOriginalIterator) { - arrayOperations = ArrayOperations.For(obj); + arrayOperations = ArrayOperations.For(obj, forWrite: false); } else {