From 1a9cd0ede6df454e9d773a55577b4df8408cc737 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 17 Jul 2021 19:24:45 +0300 Subject: [PATCH] Typed arrays (#925) --- Jint.Benchmark/Jint.Benchmark.csproj | 2 +- Jint.Benchmark/TypedArrayBenchmark.cs | 98 ++ .../Jint.Tests.CommonScripts.csproj | 2 +- Jint.Tests.Ecma/Jint.Tests.Ecma.csproj | 2 +- .../BuiltIns/TypedArrayTests.cs | 23 + Jint.Tests.Test262/Jint.Tests.Test262.csproj | 2 +- Jint.Tests.Test262/Test262Test.cs | 32 - Jint.Tests.Test262/test/skipped.json | 16 +- Jint.Tests/Jint.Tests.csproj | 2 +- Jint.Tests/Runtime/TypedArrayInteropTests.cs | 86 ++ Jint/Engine.cs | 2 +- Jint/Jint.csproj | 5 +- Jint/Native/Argument/ArgumentsInstance.cs | 15 +- Jint/Native/Array/ArrayInstance.cs | 2 +- Jint/Native/Array/ArrayOperations.cs | 123 +- Jint/Native/Array/ArrayPrototype.cs | 52 +- .../ArrayBuffer/ArrayBufferConstructor.cs | 7 +- .../Native/ArrayBuffer/ArrayBufferInstance.cs | 75 +- Jint/Native/Boolean/BooleanConstructor.cs | 2 +- Jint/Native/DataView/DataViewPrototype.cs | 6 +- Jint/Native/Date/DateConstructor.cs | 4 +- Jint/Native/Function/FunctionConstructor.cs | 2 +- Jint/Native/Function/FunctionInstance.cs | 2 +- Jint/Native/Global/GlobalObject.cs | 12 + Jint/Native/Iterator/ArrayIteratorType.cs | 9 + Jint/Native/Iterator/IteratorConstructor.cs | 44 +- Jint/Native/Iterator/IteratorInstance.cs | 155 +- Jint/Native/JsNumber.cs | 8 +- Jint/Native/JsString.cs | 1 + Jint/Native/Number/NumberConstructor.cs | 2 +- Jint/Native/Object/ObjectInstance.cs | 8 +- Jint/Native/String/StringConstructor.cs | 2 +- .../IntrinsicTypedArrayConstructor.cs | 198 +++ .../IntrinsicTypedArrayPrototype.cs | 1362 +++++++++++++++++ Jint/Native/TypedArray/TypeArrayHelper.cs | 21 + .../TypedArray/TypedArrayConstructor.Types.cs | 202 +++ .../TypedArray/TypedArrayConstructor.cs | 290 ++++ .../TypedArray/TypedArrayContentType.cs | 8 + .../TypedArray/TypedArrayElementType.cs | 51 +- Jint/Native/TypedArray/TypedArrayInstance.cs | 334 ++++ Jint/Native/TypedArray/TypedArrayPrototype.cs | 36 + .../Expressions/JintArrayExpression.cs | 4 +- .../Expressions/JintBinaryExpression.cs | 5 + .../Expressions/JintIdentifierExpression.cs | 22 +- Jint/Runtime/Intrinsics.cs | 50 + Jint/Runtime/TypeConverter.cs | 56 +- README.md | 2 +- 47 files changed, 3118 insertions(+), 326 deletions(-) create mode 100644 Jint.Benchmark/TypedArrayBenchmark.cs create mode 100644 Jint.Tests.Test262/BuiltIns/TypedArrayTests.cs create mode 100644 Jint.Tests/Runtime/TypedArrayInteropTests.cs create mode 100644 Jint/Native/Iterator/ArrayIteratorType.cs create mode 100644 Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs create mode 100644 Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs create mode 100644 Jint/Native/TypedArray/TypeArrayHelper.cs create mode 100644 Jint/Native/TypedArray/TypedArrayConstructor.Types.cs create mode 100644 Jint/Native/TypedArray/TypedArrayConstructor.cs create mode 100644 Jint/Native/TypedArray/TypedArrayContentType.cs create mode 100644 Jint/Native/TypedArray/TypedArrayInstance.cs create mode 100644 Jint/Native/TypedArray/TypedArrayPrototype.cs diff --git a/Jint.Benchmark/Jint.Benchmark.csproj b/Jint.Benchmark/Jint.Benchmark.csproj index 836d5a7388..f2cc4e9458 100644 --- a/Jint.Benchmark/Jint.Benchmark.csproj +++ b/Jint.Benchmark/Jint.Benchmark.csproj @@ -24,7 +24,7 @@ - + diff --git a/Jint.Benchmark/TypedArrayBenchmark.cs b/Jint.Benchmark/TypedArrayBenchmark.cs new file mode 100644 index 0000000000..c59354250a --- /dev/null +++ b/Jint.Benchmark/TypedArrayBenchmark.cs @@ -0,0 +1,98 @@ +using BenchmarkDotNet.Attributes; + +namespace Jint.Benchmark +{ + [MemoryDiagnoser] + public class TypedArrayBenchmark + { + private const string script = @" +var testArray = new Int32Array([29, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63]); +"; + + private Engine engine; + + + [GlobalSetup] + public void Setup() + { + engine = new Engine(); + engine.Execute(script); + } + + [Params(100)] + public int N { get; set; } + + [Benchmark] + public void Slice() + { + for (var i = 0; i < N; ++i) + { + engine.Execute("testArray.slice();"); + } + } + + [Benchmark] + public void Concat() + { + // tests conversion performance as TypedArray does not have concat + for (var i = 0; i < N; ++i) + { + engine.Execute("[].concat(testArray);"); + } + } + + [Benchmark] + public void Index() + { + for (var i = 0; i < N; ++i) + { + engine.Execute(@" +var obj2 = new Int32Array(testArray.length); +for (var i = 0, l = testArray.length; i < l; i++) { + obj2[i] = testArray[i]; +} +"); + } + } + + [Benchmark] + public void Map() + { + for (var i = 0; i < N; ++i) + { + engine.Execute(@" +var obj2 = testArray.map(function(i) { + return i; +}); +"); + } + } + + [Benchmark] + public void Apply() + { + for (var i = 0; i < N; ++i) + { + engine.Execute("Array.apply(undefined, testArray);"); + } + } + + [Benchmark] + public void JsonStringifyParse() + { + for (var i = 0; i < N; ++i) + { + engine.Execute("JSON.parse(JSON.stringify(testArray));"); + } + } + + [Benchmark] + public void FilterWithNumber() + { + for (var i = 0; i < N; ++i) + { + engine.Execute("testArray.filter(function(i) { return i > 55; });"); + } + } + } +} \ No newline at end of file diff --git a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj index e659a4324c..e728912953 100644 --- a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj +++ b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj @@ -11,7 +11,7 @@ - + diff --git a/Jint.Tests.Ecma/Jint.Tests.Ecma.csproj b/Jint.Tests.Ecma/Jint.Tests.Ecma.csproj index bbf48e7db6..7afe07800f 100644 --- a/Jint.Tests.Ecma/Jint.Tests.Ecma.csproj +++ b/Jint.Tests.Ecma/Jint.Tests.Ecma.csproj @@ -8,7 +8,7 @@ - + diff --git a/Jint.Tests.Test262/BuiltIns/TypedArrayTests.cs b/Jint.Tests.Test262/BuiltIns/TypedArrayTests.cs new file mode 100644 index 0000000000..6fe2c18f56 --- /dev/null +++ b/Jint.Tests.Test262/BuiltIns/TypedArrayTests.cs @@ -0,0 +1,23 @@ +using Xunit; + +namespace Jint.Tests.Test262.BuiltIns +{ + public class TypedArrayTests : Test262Test + { + [Theory(DisplayName = "built-ins\\TypedArray")] + [MemberData(nameof(SourceFiles), "built-ins\\TypedArray", false)] + [MemberData(nameof(SourceFiles), "built-ins\\TypedArray", true, Skip = "Skipped")] + protected void TypedArray(SourceFile sourceFile) + { + RunTestInternal(sourceFile); + } + + [Theory(DisplayName = "built-ins\\TypedArrayConstructors")] + [MemberData(nameof(SourceFiles), "built-ins\\TypedArrayConstructors", false)] + [MemberData(nameof(SourceFiles), "built-ins\\TypedArrayConstructors", true, Skip = "Skipped")] + protected void TypedArrayConstructors(SourceFile sourceFile) + { + RunTestInternal(sourceFile); + } + } +} \ No newline at end of file diff --git a/Jint.Tests.Test262/Jint.Tests.Test262.csproj b/Jint.Tests.Test262/Jint.Tests.Test262.csproj index ab176c982f..36a1abaa55 100644 --- a/Jint.Tests.Test262/Jint.Tests.Test262.csproj +++ b/Jint.Tests.Test262/Jint.Tests.Test262.csproj @@ -10,7 +10,7 @@ - + diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index 850e958599..32c4c42249 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -261,10 +261,6 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) skip = true; reason = "private/public class fields not implemented in esprima"; break; - case "super": - skip = true; - reason = "super not implemented"; - break; case "String.prototype.replaceAll": skip = true; reason = "not in spec yet"; @@ -285,22 +281,6 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) skip = true; reason = "regexp-lookbehind not implemented"; break; - case "TypedArray": - skip = true; - reason = "TypedArray not implemented"; - break; - case "Int32Array": - skip = true; - reason = "Int32Array not implemented"; - break; - case "Int8Array": - skip = true; - reason = "Int8Array not implemented"; - break; - case "Uint8Array": - skip = true; - reason = "Uint8Array not implemented"; - break; case "SharedArrayBuffer": skip = true; reason = "SharedArrayBuffer not implemented"; @@ -341,18 +321,6 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) reason = "Unicode support and its special cases need more work"; } - if (name.StartsWith("language/statements/class/subclass/builtin-objects/TypedArray")) - { - skip = true; - reason = "TypedArray not implemented"; - } - - if (name.StartsWith("language/statements/class/subclass/builtins.js")) - { - skip = true; - reason = "Uint8Array not implemented"; - } - if (name.StartsWith("built-ins/RegExp/CharacterClassEscapes/")) { skip = true; diff --git a/Jint.Tests.Test262/test/skipped.json b/Jint.Tests.Test262/test/skipped.json index 4175b35457..46a6a8ac08 100644 --- a/Jint.Tests.Test262/test/skipped.json +++ b/Jint.Tests.Test262/test/skipped.json @@ -114,14 +114,6 @@ "source": "built-ins/Proxy/enumerate/removed-does-not-trigger.js", "reason": "for-of not implemented" }, - { - "source": "built-ins/Array/prototype/concat/Array.prototype.concat_large-typed-array.js", - "reason": "Uint8Array not implemented" - }, - { - "source": "built-ins/Array/prototype/concat/Array.prototype.concat_small-typed-array.js", - "reason": "Uint8Array not implemented" - }, { "source": "built-ins/Map/prototype/forEach/iterates-values-deleted-then-readded.js", "reason": "delete/add detection not implemented for map iterator during iteration" @@ -190,6 +182,10 @@ "source": "language/expressions/object/accessor-name-computed.js", "reason": "yield not implemented" }, + { + "source": "built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js", + "reason": "yield not implemented" + }, { "source": "language/expressions/object/prop-dup-set-get-set.js", "reason": "accessor not implemented" @@ -274,6 +270,10 @@ // Esprima problems + { + "source": "language/expressions/object/method-definition/name-super-prop-param.js", + "reason": "Esprima problem" + }, { "source": "language/expressions/optional-chaining/member-expression.js", "reason": "Esprima problem" diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index 2c9e886046..cda058d632 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/Jint.Tests/Runtime/TypedArrayInteropTests.cs b/Jint.Tests/Runtime/TypedArrayInteropTests.cs new file mode 100644 index 0000000000..c79f77e007 --- /dev/null +++ b/Jint.Tests/Runtime/TypedArrayInteropTests.cs @@ -0,0 +1,86 @@ +using Xunit; + +namespace Jint.Tests.Runtime +{ + public class TypedArrayInteropTests + { + [Fact] + public void CanInteropWithInt8() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Int8Array.Construct(new sbyte[] { 42 })); + ValidateCreatedTypeArray(engine, "Int8Array"); + } + + [Fact] + public void CanInteropWithUint8() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Uint8Array.Construct(new byte[] { 42 })); + ValidateCreatedTypeArray(engine, "Uint8Array"); + } + + [Fact] + public void CanInteropWithUint8Clamped() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Uint8ClampedArray.Construct(new byte[] { 42 })); + ValidateCreatedTypeArray(engine, "Uint8ClampedArray"); + } + + [Fact] + public void CanInteropWithInt16() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Int16Array.Construct(new short[] { 42 })); + ValidateCreatedTypeArray(engine, "Int16Array"); + } + + [Fact] + public void CanInteropWithUint16() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Uint16Array.Construct(new ushort[] { 42 })); + ValidateCreatedTypeArray(engine, "Uint16Array"); + } + + [Fact] + public void CanInteropWithInt32() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Int32Array.Construct(new int[] { 42 })); + ValidateCreatedTypeArray(engine, "Int32Array"); + } + + [Fact] + public void CanInteropWithUint32() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.Uint32Array.Construct(new uint[] { 42 })); + ValidateCreatedTypeArray(engine, "Uint32Array"); + } + + [Fact(Skip = "BigInt not implemented")] + public void CanInteropWithBigInt64() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.BigInt64Array.Construct(new long[] { 42 })); + ValidateCreatedTypeArray(engine, "BigInt64Array"); + } + + [Fact(Skip = "BigInt not implemented")] + public void CanInteropWithBigUint64() + { + var engine = new Engine(); + engine.SetValue("testSubject", engine.Realm.Intrinsics.BigUint64Array.Construct(new ulong[] { 42 })); + ValidateCreatedTypeArray(engine, "BigUint64Array"); + } + + private static void ValidateCreatedTypeArray(Engine engine, string arrayName) + { + Assert.Equal(arrayName, engine.Evaluate("testSubject.constructor.name").AsString()); + Assert.Equal(1, engine.Evaluate("testSubject.length").AsNumber()); + Assert.Equal(42, engine.Evaluate("testSubject[0]").AsNumber()); + } + } +} \ No newline at end of file diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 10fcc836c2..c274d3e080 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -422,7 +422,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) { var baseValue = reference.GetBase(); - if (baseValue._type == InternalTypes.Undefined) + if (baseValue.IsUndefined()) { if (_referenceResolver.TryUnresolvableReference(this, reference, out JsValue val)) { diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index 3482123d5a..e16df4d834 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -9,10 +9,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Jint/Native/Argument/ArgumentsInstance.cs b/Jint/Native/Argument/ArgumentsInstance.cs index 95dca23ce1..c7799ac1fd 100644 --- a/Jint/Native/Argument/ArgumentsInstance.cs +++ b/Jint/Native/Argument/ArgumentsInstance.cs @@ -25,9 +25,10 @@ public sealed class ArgumentsInstance : ObjectInstance private DeclarativeEnvironmentRecord _env; private bool _canReturnToPool; private bool _hasRestParameter; + private bool _materialized; internal ArgumentsInstance(Engine engine) - : base(engine, ObjectClass.Arguments, InternalTypes.Object | InternalTypes.RequiresCloning) + : base(engine, ObjectClass.Arguments) { } @@ -236,9 +237,15 @@ public override bool Delete(JsValue property) return base.Delete(property); } - internal override JsValue DoClone() + internal void Materialize() { - // there's an assignment or return value of function, need to create persistent state + if (_materialized) + { + // already done + return; + } + + _materialized = true; EnsureInitialized(); @@ -248,8 +255,6 @@ internal override JsValue DoClone() _args = copiedArgs; _canReturnToPool = false; - - return this; } internal void FunctionWasCalled() diff --git a/Jint/Native/Array/ArrayInstance.cs b/Jint/Native/Array/ArrayInstance.cs index a778ae6677..6f838dac4b 100644 --- a/Jint/Native/Array/ArrayInstance.cs +++ b/Jint/Native/Array/ArrayInstance.cs @@ -421,7 +421,7 @@ public override void RemoveOwnProperty(JsValue p) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsArrayIndex(JsValue p, out uint index) + private static bool IsArrayIndex(JsValue p, out uint index) { if (p is JsNumber number) { diff --git a/Jint/Native/Array/ArrayOperations.cs b/Jint/Native/Array/ArrayOperations.cs index 9bc8ea6f7a..30df2417d6 100644 --- a/Jint/Native/Array/ArrayOperations.cs +++ b/Jint/Native/Array/ArrayOperations.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Jint.Native.Number; using Jint.Native.Object; +using Jint.Native.TypedArray; using Jint.Runtime; using Jint.Runtime.Descriptors; @@ -19,6 +20,11 @@ public static ArrayOperations For(ObjectInstance instance) return new ArrayInstanceOperations(arrayInstance); } + if (instance is TypedArrayInstance typedArrayInstance) + { + return new TypedArrayInstanceOperations(typedArrayInstance); + } + return new ObjectInstanceOperations(instance); } @@ -199,18 +205,14 @@ public override ulong GetLongLength() } public override void SetLength(ulong length) - { - _target.Set(CommonProperties.Length, length, true); - } + => _target.Set(CommonProperties.Length, length, true); public override void EnsureCapacity(ulong capacity) { } public override JsValue Get(ulong index) - { - return _target.Get(JsString.Create(index), _target); - } + => _target.Get(JsString.Create(index), _target); public override bool TryGetValue(ulong index, out JsValue value) { @@ -222,19 +224,13 @@ public override bool TryGetValue(ulong index, out JsValue value) } public override void CreateDataPropertyOrThrow(ulong index, JsValue value) - { - _target.CreateDataPropertyOrThrow(JsString.Create(index), value); - } + => _target.CreateDataPropertyOrThrow(JsString.Create(index), value); public override void Set(ulong index, JsValue value, bool updateLength, bool throwOnError) - { - _target.Set(JsString.Create(index), value, throwOnError); - } + => _target.Set(JsString.Create(index), value, throwOnError); public override void DeletePropertyOrThrow(ulong index) - { - _target.DeletePropertyOrThrow(JsString.Create(index)); - } + => _target.DeletePropertyOrThrow(JsString.Create(index)); } private sealed class ArrayInstanceOperations : ArrayOperations @@ -244,40 +240,25 @@ public ArrayInstanceOperations(ArrayInstance target) : base(target) } public override ulong GetSmallestIndex(ulong length) - { - return _target.GetSmallestIndex(); - } + => _target.GetSmallestIndex(); public override uint GetLength() - { - return (uint) ((JsNumber) _target._length._value)._value; - } + => (uint) ((JsNumber) _target._length._value)._value; public override ulong GetLongLength() - { - return (ulong) ((JsNumber) _target._length._value)._value; - } + => (ulong) ((JsNumber) _target._length._value)._value; public override void SetLength(ulong length) - { - _target.Set(CommonProperties.Length, length, true); - } + => _target.Set(CommonProperties.Length, length, true); public override void EnsureCapacity(ulong capacity) - { - _target.EnsureCapacity((uint) capacity); - } + => _target.EnsureCapacity((uint) capacity); public override bool TryGetValue(ulong index, out JsValue value) - { // array max size is uint - return _target.TryGetValue((uint) index, out value); - } + => _target.TryGetValue((uint) index, out value); - public override JsValue Get(ulong index) - { - return _target.Get((uint) index); - } + public override JsValue Get(ulong index) => _target.Get((uint) index); public override JsValue[] GetAll(Types elementTypes) { @@ -311,20 +292,78 @@ public override JsValue[] GetAll(Types elementTypes) } public override void DeletePropertyOrThrow(ulong index) + => _target.DeletePropertyOrThrow((uint) index); + + public override void CreateDataPropertyOrThrow(ulong index, JsValue value) + => _target.SetIndexValue((uint) index, value, updateLength: false); + + public override void Set(ulong index, JsValue value, bool updateLength, bool throwOnError) + => _target.SetIndexValue((uint) index, value, updateLength); + } + + private sealed class TypedArrayInstanceOperations : ArrayOperations + { + private readonly TypedArrayInstance _target; + + public TypedArrayInstanceOperations(TypedArrayInstance target) { - _target.DeletePropertyOrThrow((uint) index); + _target = target; } - public override void CreateDataPropertyOrThrow(ulong index, JsValue value) + public override ObjectInstance Target => _target; + + public override ulong GetSmallestIndex(ulong length) => 0; + + public override uint GetLength() { - _target.SetIndexValue((uint) index, value, updateLength: false); + if (!_target.IsConcatSpreadable) + { + return _target.Length; + } + + var descValue = _target.Get(CommonProperties.Length, _target); + if (!ReferenceEquals(descValue, null)) + { + return (uint) TypeConverter.ToInteger(descValue); + } + + return 0; } - public override void Set(ulong index, JsValue value, bool updateLength, bool throwOnError) + public override ulong GetLongLength() => GetLength(); + + public override void SetLength(ulong length) { - _target.SetIndexValue((uint) index, value, updateLength); } + + public override void EnsureCapacity(ulong capacity) + { + } + + public override JsValue Get(ulong index) => _target[(int) index]; + + public override bool TryGetValue(ulong index, out JsValue value) + { + if (index < _target.Length) + { + value = _target[(int) index]; + return true; + } + + value = JsValue.Undefined; + return false; + } + + public override void CreateDataPropertyOrThrow(ulong index, JsValue value) + => _target.CreateDataPropertyOrThrow(index, value); + + public override void Set(ulong index, JsValue value, bool updateLength, bool throwOnError) + => _target[(int) index] = value; + + public override void DeletePropertyOrThrow(ulong index) + => _target.DeletePropertyOrThrow(index); } + } /// diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index f5bae861e3..8233dd6a97 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Jint.Collections; +using Jint.Native.Iterator; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.Symbol; @@ -105,7 +106,7 @@ private ObjectInstance Keys(JsValue thisObj, JsValue[] arguments) { if (thisObj is ObjectInstance oi && oi.IsArrayLike) { - return _realm.Intrinsics.Iterator.ConstructArrayLikeKeyIterator(oi); + return _realm.Intrinsics.Iterator.CreateArrayLikeIterator(oi, ArrayIteratorType.Key); } ExceptionHelper.ThrowTypeError(_realm, "cannot construct iterator"); @@ -116,7 +117,7 @@ internal ObjectInstance Values(JsValue thisObj, JsValue[] arguments) { if (thisObj is ObjectInstance oi && oi.IsArrayLike) { - return _realm.Intrinsics.Iterator.ConstructArrayLikeValueIterator(oi); + return _realm.Intrinsics.Iterator.CreateArrayLikeIterator(oi, ArrayIteratorType.Value); } ExceptionHelper.ThrowTypeError(_realm, "cannot construct iterator"); @@ -127,7 +128,7 @@ private ObjectInstance Entries(JsValue thisObj, JsValue[] arguments) { if (thisObj is ObjectInstance oi && oi.IsArrayLike) { - return _realm.Intrinsics.Iterator.ConstructArrayLikeEntriesIterator(oi); + return _realm.Intrinsics.Iterator.CreateArrayLikeIterator(oi, ArrayIteratorType.KeyAndValue); } ExceptionHelper.ThrowTypeError(_realm, "cannot construct iterator"); @@ -614,15 +615,10 @@ private JsValue Includes(JsValue thisObj, JsValue[] arguments) ? n : len - System.Math.Abs(n), 0); - static bool SameValueZero(JsValue x, JsValue y) - { - return x == y || (x is JsNumber xNum && y is JsNumber yNum && double.IsNaN(xNum._value) && double.IsNaN(yNum._value)); - } - while (k < len) { var value = o.Get(k); - if (SameValueZero(value, searchElement)) + if (JintBinaryExpression.SameValueZero(value, searchElement)) { return true; } @@ -908,7 +904,7 @@ private JsValue Sort(JsValue thisObj, JsValue[] arguments) { if (!thisObj.IsObject()) { - ExceptionHelper.ThrowTypeError(_realm, "Array.prorotype.sort can only be applied on objects"); + ExceptionHelper.ThrowTypeError(_realm, "Array.prototype.sort can only be applied on objects"); } var obj = ArrayOperations.For(thisObj.AsObject()); @@ -946,17 +942,17 @@ private JsValue Sort(JsValue thisObj, JsValue[] arguments) { obj.DeletePropertyOrThrow(i); } - } + } } catch (InvalidOperationException e) { - throw e.InnerException; + throw e.InnerException ?? e; } return obj.Target; } - internal JsValue Slice(JsValue thisObj, JsValue[] arguments) + private JsValue Slice(JsValue thisObj, JsValue[] arguments) { var start = arguments.At(0); var end = arguments.At(1); @@ -989,7 +985,7 @@ internal JsValue Slice(JsValue thisObj, JsValue[] arguments) } else { - final = (ulong) System.Math.Min(TypeConverter.ToInteger(relativeEnd), len); + final = (ulong) System.Math.Min(relativeEnd, len); } } @@ -1091,17 +1087,13 @@ private JsValue Reverse(JsValue thisObj, JsValue[] arguments) return o.Target; } - private JsValue Join(JsValue thisObj, JsValue[] arguments) + internal JsValue Join(JsValue thisObj, JsValue[] arguments) { var separator = arguments.At(0); var o = ArrayOperations.For(_realm, thisObj); var len = o.GetLength(); - if (separator.IsUndefined()) - { - separator = ","; - } - var sep = TypeConverter.ToString(separator); + var sep = TypeConverter.ToString(separator.IsUndefined() ? JsString.CommaString : separator); // as per the spec, this has to be called after ToString(separator) if (len == 0) @@ -1109,7 +1101,7 @@ private JsValue Join(JsValue thisObj, JsValue[] arguments) return JsString.Empty; } - string StringFromJsValue(JsValue value) + static string StringFromJsValue(JsValue value) { return value.IsNullOrUndefined() ? "" @@ -1122,17 +1114,15 @@ string StringFromJsValue(JsValue value) return s; } - using (var sb = StringBuilderPool.Rent()) + using var sb = StringBuilderPool.Rent(); + sb.Builder.Append(s); + for (uint k = 1; k < len; k++) { - sb.Builder.Append(s); - for (uint k = 1; k < len; k++) - { - sb.Builder.Append(sep); - sb.Builder.Append(StringFromJsValue(o.Get(k))); - } - - return sb.ToString(); + sb.Builder.Append(sep); + sb.Builder.Append(StringFromJsValue(o.Get(k))); } + + return sb.ToString(); } private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments) @@ -1198,7 +1188,7 @@ private JsValue Concat(JsValue thisObj, JsValue[] arguments) for (var i = 0; i < items.Count; i++) { ulong increment; - if (!(items[i] is ObjectInstance objectInstance)) + if (items[i] is not ObjectInstance objectInstance) { increment = 1; } diff --git a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs index 33bd449375..e7eb36452b 100644 --- a/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs +++ b/Jint/Native/ArrayBuffer/ArrayBufferConstructor.cs @@ -3,6 +3,7 @@ using Jint.Native.Function; using Jint.Native.Object; using Jint.Native.Symbol; +using Jint.Native.TypedArray; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; @@ -53,7 +54,7 @@ protected override void Initialize() private static JsValue IsView(JsValue thisObject, JsValue[] arguments) { var arg = arguments.At(0); - return arg is DataViewInstance; + return arg is DataViewInstance or TypedArrayInstance; } /// @@ -81,12 +82,12 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return AllocateArrayBuffer(newTarget, byteLength); } - internal ArrayBufferInstance AllocateArrayBuffer(JsValue constructor, uint byteLength) + internal ArrayBufferInstance AllocateArrayBuffer(JsValue constructor, ulong byteLength) { var obj = OrdinaryCreateFromConstructor( constructor, static intrinsics => intrinsics.ArrayBuffer.PrototypeObject, - static (engine, realm, state) => new ArrayBufferInstance(engine, (uint) ((JsNumber) state)._value), + static (engine, realm, state) => new ArrayBufferInstance(engine, (ulong) ((JsNumber) state)._value), JsNumber.Create(byteLength)); return obj; diff --git a/Jint/Native/ArrayBuffer/ArrayBufferInstance.cs b/Jint/Native/ArrayBuffer/ArrayBufferInstance.cs index a13e27ce98..212a768677 100644 --- a/Jint/Native/ArrayBuffer/ArrayBufferInstance.cs +++ b/Jint/Native/ArrayBuffer/ArrayBufferInstance.cs @@ -11,20 +11,20 @@ namespace Jint.Native.ArrayBuffer public sealed class ArrayBufferInstance : ObjectInstance { // so that we don't need to allocate while or reading setting values - private byte[] _workBuffer = new byte[8]; + private readonly byte[] _workBuffer = new byte[8]; private byte[] _arrayBufferData; - private JsValue _arrayBufferDetachKey = Undefined; + private readonly JsValue _arrayBufferDetachKey = Undefined; internal ArrayBufferInstance( Engine engine, - uint byteLength) : base(engine) + ulong byteLength) : base(engine) { - var block = CreateByteDataBlock(byteLength); + var block = byteLength > 0 ? CreateByteDataBlock(byteLength) : System.Array.Empty(); _arrayBufferData = block; } - private byte[] CreateByteDataBlock(uint byteLength) + private byte[] CreateByteDataBlock(ulong byteLength) { if (byteLength > int.MaxValue) { @@ -55,15 +55,42 @@ internal void DetachArrayBuffer(JsValue key = null) _arrayBufferData = null; } + /// + /// https://tc39.es/ecma262/#sec-clonearraybuffer + /// + internal ArrayBufferInstance CloneArrayBuffer( + ArrayBufferConstructor constructor, + int srcByteOffset, + uint srcLength, + JsValue cloneConstructor) + { + var targetBuffer = constructor.AllocateArrayBuffer(cloneConstructor, srcLength); + AssertNotDetached(); + var srcBlock = _arrayBufferData; + var targetBlock = targetBuffer.ArrayBufferData; + + // TODO SharedArrayBuffer would use this + //CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). + + System.Array.Copy(srcBlock, srcByteOffset, targetBlock, 0, srcLength); + + return targetBuffer; + } + /// /// https://tc39.es/ecma262/#sec-getvaluefrombuffer /// - internal JsValue GetValueFromBuffer(uint byteIndex, TypedArrayElementType type, bool isTypedArray, ArrayBufferOrder order, bool? isLittleEndian = null) + internal double GetValueFromBuffer( + int byteIndex, + TypedArrayElementType type, + bool isTypedArray, + ArrayBufferOrder order, + bool? isLittleEndian = null) { if (!IsSharedArrayBuffer) { // If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. - return RawBytesToNumeric(type, (int) byteIndex, isLittleEndian ?? BitConverter.IsLittleEndian); + return RawBytesToNumeric(type, byteIndex, isLittleEndian ?? BitConverter.IsLittleEndian); } /* @@ -77,13 +104,13 @@ g. Append readEvent to eventList. h. Append Chosen Value EsprimaExtensions.Record { [[Event]]: readEvent, [[ChosenValue]]: rawValue } to execution.[[ChosenValues]]. */ ExceptionHelper.ThrowNotImplementedException("SharedArrayBuffer not implemented"); - return Undefined; + return 0; } /// /// https://tc39.es/ecma262/#sec-rawbytestonumeric /// - private JsValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, bool isLittleEndian) + private double RawBytesToNumeric(TypedArrayElementType type, int byteIndex, bool isLittleEndian) { var elementSize = type.GetElementSize(); var rawBytes = _arrayBufferData; @@ -106,17 +133,17 @@ private JsValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, boo // If value is an IEEE 754-2019 binary32 NaN value, return the NaN Number value. if (float.IsNaN(value)) { - return JsNumber.DoubleNaN; + return double.NaN; } - return JsNumber.Create(value); + return value; } if (type == TypedArrayElementType.Float64) { // rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary64 value. var value = BitConverter.ToDouble(rawBytes, byteIndex); - return JsNumber.Create(value); + return value; } if (type.IsBigIntElementType()) @@ -125,24 +152,24 @@ private JsValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, boo ExceptionHelper.ThrowNotImplementedException("BigInt not implemented"); } - var intValue = type switch + long? intValue = type switch { - TypedArrayElementType.Int8 => JsNumber.Create((sbyte) rawBytes[byteIndex]), - TypedArrayElementType.Uint8 => JsNumber.Create(rawBytes[byteIndex]), - TypedArrayElementType.Uint8C => JsNumber.Create(rawBytes[byteIndex]), - TypedArrayElementType.Int16 => JsNumber.Create(isLittleEndian + TypedArrayElementType.Int8 => ((sbyte) rawBytes[byteIndex]), + TypedArrayElementType.Uint8 => (rawBytes[byteIndex]), + TypedArrayElementType.Uint8C =>(rawBytes[byteIndex]), + TypedArrayElementType.Int16 => (isLittleEndian ? (short) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8)) : (short) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)) ), - TypedArrayElementType.Uint16 => JsNumber.Create(isLittleEndian + TypedArrayElementType.Uint16 => (isLittleEndian ? (ushort) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8)) : (ushort) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)) ), - TypedArrayElementType.Int32 => JsNumber.Create(isLittleEndian + TypedArrayElementType.Int32 => (isLittleEndian ? rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24) : rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex + 0] << 24) ), - TypedArrayElementType.Uint32 => JsNumber.Create(isLittleEndian + TypedArrayElementType.Uint32 => (isLittleEndian ? (uint) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24)) : (uint) (rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex] << 24)) ), @@ -154,14 +181,14 @@ private JsValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, boo ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(type), type.ToString()); } - return intValue; + return (double) intValue; } /// /// https://tc39.es/ecma262/#sec-setvalueinbuffer /// internal void SetValueInBuffer( - uint byteIndex, + int byteIndex, TypedArrayElementType type, double value, bool isTypedArray, @@ -173,7 +200,7 @@ internal void SetValueInBuffer( { // If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. var rawBytes = NumericToRawBytes(type, value, isLittleEndian ?? BitConverter.IsLittleEndian); - System.Array.Copy(rawBytes, 0, block, (int) byteIndex, type.GetElementSize()); + System.Array.Copy(rawBytes, 0, block, byteIndex, type.GetElementSize()); } else { @@ -214,7 +241,7 @@ private byte[] NumericToRawBytes(TypedArrayElementType type, double value, bool rawBytes[0] = (byte) intValue; break; case TypedArrayElementType.Uint8C: - rawBytes[0] = System.Math.Min(System.Math.Max((byte) intValue, (byte) 0), (byte) 255); + rawBytes[0] = (byte) TypeConverter.ToUint8Clamp(value); break; case TypedArrayElementType.Int16: #if !NETSTANDARD2_1 diff --git a/Jint/Native/Boolean/BooleanConstructor.cs b/Jint/Native/Boolean/BooleanConstructor.cs index 67b3e93e14..2a88486fa6 100644 --- a/Jint/Native/Boolean/BooleanConstructor.cs +++ b/Jint/Native/Boolean/BooleanConstructor.cs @@ -18,7 +18,7 @@ internal BooleanConstructor( { _prototype = functionPrototype; PrototypeObject = new BooleanPrototype(engine, realm, this, objectPrototype); - _length = new PropertyDescriptor(JsNumber.One, PropertyFlag.Configurable); + _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable); _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } diff --git a/Jint/Native/DataView/DataViewPrototype.cs b/Jint/Native/DataView/DataViewPrototype.cs index 40b78117ce..0e1a64f76c 100644 --- a/Jint/Native/DataView/DataViewPrototype.cs +++ b/Jint/Native/DataView/DataViewPrototype.cs @@ -231,7 +231,7 @@ private JsValue GetViewValue( ExceptionHelper.ThrowTypeError(_realm, "Method called on incompatible receiver " + view); } - var getIndex = TypeConverter.ToIndex(_realm, requestIndex); + var getIndex = (int) TypeConverter.ToIndex(_realm, requestIndex); var isLittleEndianBoolean = TypeConverter.ToBoolean(isLittleEndian); var buffer = dataView._viewedArrayBuffer; @@ -245,7 +245,7 @@ private JsValue GetViewValue( ExceptionHelper.ThrowRangeError(_realm, "Offset is outside the bounds of the DataView"); } - var bufferIndex = getIndex + viewOffset; + var bufferIndex = (int) (getIndex + viewOffset); return buffer.GetValueFromBuffer(bufferIndex, type, false, ArrayBufferOrder.Unordered, isLittleEndianBoolean); } @@ -291,7 +291,7 @@ private JsValue SetViewValue( ExceptionHelper.ThrowRangeError(_realm, "Offset is outside the bounds of the DataView"); } - var bufferIndex = getIndex + viewOffset; + var bufferIndex = (int) (getIndex + viewOffset); buffer.SetValueInBuffer(bufferIndex, type, numberValue, false, ArrayBufferOrder.Unordered, isLittleEndianBoolean); return Undefined; } diff --git a/Jint/Native/Date/DateConstructor.cs b/Jint/Native/Date/DateConstructor.cs index 82995bfdb0..f4dfaed43d 100644 --- a/Jint/Native/Date/DateConstructor.cs +++ b/Jint/Native/Date/DateConstructor.cs @@ -107,7 +107,7 @@ private JsValue Utc(JsValue thisObj, JsValue[] arguments) { var y = TypeConverter.ToNumber(arguments.At(0)); var m = TypeConverter.ToNumber(arguments.At(1, JsNumber.PositiveZero)); - var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.One)); + var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne)); var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero)); var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero)); var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero)); @@ -165,7 +165,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { var y = TypeConverter.ToNumber(arguments.At(0)); var m = TypeConverter.ToNumber(arguments.At(1)); - var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.One)); + var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne)); var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero)); var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero)); var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero)); diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index 2f01faf4e8..e0ea70eaf9 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -22,7 +22,7 @@ internal FunctionConstructor( PrototypeObject = new FunctionPrototype(engine, realm, objectPrototype); _prototype = PrototypeObject; _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); - _length = new PropertyDescriptor(JsNumber.One, PropertyFlag.Configurable); + _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable); } public FunctionPrototype PrototypeObject { get; } diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index d839b2b248..5fe1c29b38 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -25,7 +25,7 @@ public abstract class FunctionInstance : ObjectInstance, ICallable internal ConstructorKind _constructorKind = ConstructorKind.Base; internal Realm _realm; - private PrivateEnvironmentRecord _privateEnvironment; + private PrivateEnvironmentRecord _privateEnvironment; protected FunctionInstance( Engine engine, diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs index 1b595f9162..bdc29b4ddf 100644 --- a/Jint/Native/Global/GlobalObject.cs +++ b/Jint/Native/Global/GlobalObject.cs @@ -41,6 +41,18 @@ protected override void Initialize() ["Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Array, propertyFlags), ["ArrayBuffer"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.ArrayBuffer, propertyFlags), ["DataView"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.DataView, propertyFlags), + ["TypedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.TypedArray, propertyFlags), + ["Int8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Int8Array, propertyFlags), + ["Uint8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Uint8Array, propertyFlags), + ["Uint8ClampedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Uint8ClampedArray, propertyFlags), + ["Int16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Int16Array, propertyFlags), + ["Uint16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Uint16Array, propertyFlags), + ["Int32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Int32Array, propertyFlags), + ["Uint32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Uint32Array, propertyFlags), + ["BigInt64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.BigInt64Array, propertyFlags), + ["BigUint64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.BigUint64Array, propertyFlags), + ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Float32Array, propertyFlags), + ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Float64Array, propertyFlags), ["Map"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Map, propertyFlags), ["Set"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Set, propertyFlags), ["WeakMap"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.WeakMap, propertyFlags), diff --git a/Jint/Native/Iterator/ArrayIteratorType.cs b/Jint/Native/Iterator/ArrayIteratorType.cs new file mode 100644 index 0000000000..55c634d6d1 --- /dev/null +++ b/Jint/Native/Iterator/ArrayIteratorType.cs @@ -0,0 +1,9 @@ +namespace Jint.Native.Iterator +{ + internal enum ArrayIteratorType + { + Key, + Value, + KeyAndValue + } +} \ No newline at end of file diff --git a/Jint/Native/Iterator/IteratorConstructor.cs b/Jint/Native/Iterator/IteratorConstructor.cs index 6df2eadbf1..dc8893cdc3 100644 --- a/Jint/Native/Iterator/IteratorConstructor.cs +++ b/Jint/Native/Iterator/IteratorConstructor.cs @@ -44,7 +44,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) return Construct(Enumerable.Empty()); } - internal ObjectInstance Construct(IEnumerable enumerable) + internal IteratorInstance Construct(IEnumerable enumerable) { var instance = new IteratorInstance(Engine, enumerable) { @@ -54,7 +54,7 @@ internal ObjectInstance Construct(IEnumerable enumerable) return instance; } - internal ObjectInstance Construct(List enumerable) + internal IteratorInstance Construct(List enumerable) { var instance = new IteratorInstance.ListIterator(Engine, enumerable) { @@ -64,9 +64,9 @@ internal ObjectInstance Construct(List enumerable) return instance; } - internal ObjectInstance Construct(ObjectInstance array, Func prototypeSelector) + internal IteratorInstance Construct(ObjectInstance array, Func prototypeSelector) { - var instance = new IteratorInstance.ArrayLikeIterator(Engine, array) + var instance = new IteratorInstance.ArrayLikeIterator(Engine, array, ArrayIteratorType.KeyAndValue) { _prototype = prototypeSelector(_realm.Intrinsics) }; @@ -74,7 +74,7 @@ internal ObjectInstance Construct(ObjectInstance array, Func _values; private int _position; @@ -215,80 +179,67 @@ public override bool TryIteratorStep(out ObjectInstance nextItem) } } - public class ArrayLikeKeyIterator : IteratorInstance + internal sealed class ArrayLikeIterator : IteratorInstance { + private readonly ArrayIteratorType _kind; + private readonly TypedArrayInstance _typedArray; private readonly ArrayOperations _operations; private uint _position; private bool _closed; - public ArrayLikeKeyIterator(Engine engine, ObjectInstance objectInstance) : base(engine) + public ArrayLikeIterator(Engine engine, ObjectInstance objectInstance, ArrayIteratorType kind) : base(engine) { - _operations = ArrayOperations.For(objectInstance); - _position = 0; - } - - public override bool TryIteratorStep(out ObjectInstance nextItem) - { - var length = _operations.GetLength(); - if (!_closed && _position < length) + _kind = kind; + _typedArray = objectInstance as TypedArrayInstance; + if (_typedArray is null) { - nextItem = new ValueIteratorPosition(_engine, _position++); - return true; + _operations = ArrayOperations.For(objectInstance); } - - _closed = true; - nextItem = KeyValueIteratorPosition.Done; - return false; - } - } - - public class ArrayLikeValueIterator : IteratorInstance - { - private readonly ArrayOperations _operations; - private uint _position; - private bool _closed; - - public ArrayLikeValueIterator(Engine engine, ObjectInstance objectInstance) : base(engine) - { - _operations = ArrayOperations.For(objectInstance); _position = 0; } public override bool TryIteratorStep(out ObjectInstance nextItem) { - var length = _operations.GetLength(); - if (!_closed && _position < length) + uint len; + if (_typedArray is not null) { - _operations.TryGetValue(_position++, out var value); - nextItem = new ValueIteratorPosition(_engine, value); - return true; + _typedArray._viewedArrayBuffer.AssertNotDetached(); + len = _typedArray.Length; + } + else + { + len = _operations.GetLength(); } - _closed = true; - nextItem = KeyValueIteratorPosition.Done; - return false; - } - } - - public class ArrayLikeEntriesIterator : IteratorInstance - { - private readonly ArrayOperations _operations; - private uint _position; - private bool _closed; - - public ArrayLikeEntriesIterator(Engine engine, ObjectInstance objectInstance) : base(engine) - { - _operations = ArrayOperations.For(objectInstance); - _position = 0; - } - - public override bool TryIteratorStep(out ObjectInstance nextItem) - { - var length = _operations.GetLength(); - if (!_closed && _position < length) + if (!_closed && _position < len) { - _operations.TryGetValue(_position, out var value); - nextItem = new KeyValueIteratorPosition(_engine, _position, value); + JsValue value; + if (_typedArray is not null) + { + nextItem = _kind switch + { + ArrayIteratorType.Key => new ValueIteratorPosition(_engine, _position), + ArrayIteratorType.Value => new ValueIteratorPosition(_engine, _typedArray[(int) _position]), + _ => new KeyValueIteratorPosition(_engine, _position, _typedArray[(int) _position]) + }; + } + else + { + _operations.TryGetValue(_position, out value); + if (_kind == ArrayIteratorType.Key) + { + nextItem = new ValueIteratorPosition(_engine, _position); + } + else if (_kind == ArrayIteratorType.Value) + { + nextItem = new ValueIteratorPosition(_engine, value); + } + else + { + nextItem = new KeyValueIteratorPosition(_engine, _position, value); + } + } + _position++; return true; } @@ -299,7 +250,7 @@ public override bool TryIteratorStep(out ObjectInstance nextItem) } } - internal class ObjectIterator : IIterator + internal sealed class ObjectIterator : IIterator { private readonly ObjectInstance _target; private readonly ICallable _nextMethod; @@ -372,7 +323,7 @@ public void Close(CompletionType completion) } } - internal class StringIterator : IteratorInstance + internal sealed class StringIterator : IteratorInstance { private readonly TextElementEnumerator _iterator; @@ -394,7 +345,7 @@ public override bool TryIteratorStep(out ObjectInstance nextItem) } } - internal class RegExpStringIterator : IteratorInstance + internal sealed class RegExpStringIterator : IteratorInstance { private readonly RegExpInstance _iteratingRegExp; private readonly string _s; diff --git a/Jint/Native/JsNumber.cs b/Jint/Native/JsNumber.cs index 50451f917a..ca6eb98037 100644 --- a/Jint/Native/JsNumber.cs +++ b/Jint/Native/JsNumber.cs @@ -25,12 +25,12 @@ public sealed class JsNumber : JsValue, IEquatable internal static readonly JsNumber DoubleNegativeOne = new JsNumber((double) -1); internal static readonly JsNumber DoublePositiveInfinity = new JsNumber(double.PositiveInfinity); internal static readonly JsNumber DoubleNegativeInfinity = new JsNumber(double.NegativeInfinity); - private static readonly JsNumber IntegerNegativeOne = new JsNumber(-1); + internal static readonly JsNumber IntegerNegativeOne = new JsNumber(-1); internal static readonly JsNumber NegativeZero = new JsNumber(-0d); internal static readonly JsNumber PositiveZero = new JsNumber(+0); - internal static readonly JsNumber One = new JsNumber(1); - internal static readonly JsNumber Two = new JsNumber(2); - internal static readonly JsNumber Three = new JsNumber(3); + internal static readonly JsNumber PositiveOne = new JsNumber(1); + internal static readonly JsNumber PositiveTwo = new JsNumber(2); + internal static readonly JsNumber PositiveThree = new JsNumber(3); internal static readonly JsNumber PI = new JsNumber(System.Math.PI); diff --git a/Jint/Native/JsString.cs b/Jint/Native/JsString.cs index 9e687894c7..2a4d36739a 100644 --- a/Jint/Native/JsString.cs +++ b/Jint/Native/JsString.cs @@ -27,6 +27,7 @@ public class JsString : JsValue, IEquatable internal static readonly JsString TrueString = new JsString("true"); internal static readonly JsString FalseString = new JsString("false"); internal static readonly JsString LengthString = new JsString("length"); + internal static readonly JsValue CommaString = new JsString(","); internal string _value; diff --git a/Jint/Native/Number/NumberConstructor.cs b/Jint/Native/Number/NumberConstructor.cs index 8b92cd20ec..045a2e6a7d 100644 --- a/Jint/Native/Number/NumberConstructor.cs +++ b/Jint/Native/Number/NumberConstructor.cs @@ -24,7 +24,7 @@ public NumberConstructor( { _prototype = functionPrototype; PrototypeObject = new NumberPrototype(engine, realm, this, objectPrototype); - _length = new PropertyDescriptor(JsNumber.One, PropertyFlag.Configurable); + _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable); _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 81c9128f11..2dd289a5fb 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -83,7 +83,7 @@ internal ObjectClass Class /// /// https://tc39.es/ecma262/#sec-construct /// - internal ObjectInstance Construct(IConstructor f, JsValue[] argumentsList = null, IConstructor newTarget = null) + internal static ObjectInstance Construct(IConstructor f, JsValue[] argumentsList = null, IConstructor newTarget = null) { newTarget ??= f; argumentsList ??= System.Array.Empty(); @@ -893,15 +893,15 @@ public override object ToObject() case ObjectClass.Array: if (this is ArrayInstance arrayInstance) { - var len = TypeConverter.ToInt32(arrayInstance.Get(CommonProperties.Length, arrayInstance)); + var len = arrayInstance.Length; var result = new object[len]; - for (var k = 0; k < len; k++) + for (uint k = 0; k < len; k++) { var pk = TypeConverter.ToJsString(k); var kpresent = arrayInstance.HasProperty(pk); if (kpresent) { - var kvalue = arrayInstance.Get(pk, arrayInstance); + var kvalue = arrayInstance.Get(k); result[k] = kvalue.ToObject(); } else diff --git a/Jint/Native/String/StringConstructor.cs b/Jint/Native/String/StringConstructor.cs index aa33e07377..9f028a1f1c 100644 --- a/Jint/Native/String/StringConstructor.cs +++ b/Jint/Native/String/StringConstructor.cs @@ -25,7 +25,7 @@ public StringConstructor( { _prototype = functionPrototype; PrototypeObject = new StringPrototype(engine, realm, this, objectPrototype); - _length = new PropertyDescriptor(JsNumber.One, PropertyFlag.Configurable); + _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable); _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs new file mode 100644 index 0000000000..189a05120e --- /dev/null +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayConstructor.cs @@ -0,0 +1,198 @@ +using Jint.Collections; +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Native.TypedArray +{ + /// + /// https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object + /// + internal class IntrinsicTypedArrayConstructor : FunctionInstance, IConstructor + { + internal IntrinsicTypedArrayConstructor( + Engine engine, + Realm realm, + ObjectInstance functionPrototype, + ObjectInstance objectPrototype, + string functionName) : base(engine, realm, new JsString(functionName)) + { + _prototype = functionPrototype; + PrototypeObject = new IntrinsicTypedArrayPrototype(engine, realm, objectPrototype, this); + _length = new PropertyDescriptor(JsNumber.PositiveZero, PropertyFlag.Configurable); + _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); + } + + public IntrinsicTypedArrayPrototype PrototypeObject { get; } + + protected override void Initialize() + { + var properties = new PropertyDictionary(2, false) + { + ["from"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "from", From, 1, PropertyFlag.Configurable), PropertyFlag.NonEnumerable)), + ["of"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "of", Of, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable)) + }; + SetProperties(properties); + + var symbols = new SymbolDictionary(1) + { + [GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(new ClrFunctionInstance(Engine, "get [Symbol.species]", Species, 0, PropertyFlag.Configurable), Undefined, PropertyFlag.Configurable) + }; + SetSymbols(symbols); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.from + /// + private JsValue From(JsValue thisObj, JsValue[] arguments) + { + var c = thisObj; + if (!c.IsConstructor) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var source = arguments.At(0); + var mapFunction = arguments.At(1); + var thisArg = arguments.At(2); + + var mapping = !mapFunction.IsUndefined(); + if (mapping) + { + if (!mapFunction.IsCallable) + { + ExceptionHelper.ThrowTypeError(_realm); + } + } + + var usingIterator = GetMethod(_realm, source, GlobalSymbolRegistry.Iterator); + if (usingIterator is not null) + { + var values = TypedArrayConstructor.IterableToList(_realm, source, usingIterator); + var iteratorLen = values.Count; + var iteratorTarget = TypedArrayCreate((IConstructor) c, new JsValue[] { iteratorLen }); + for (var k = 0; k < iteratorLen; ++k) + { + var kValue = values[k]; + var mappedValue = mapping + ? ((ICallable) mapFunction).Call(thisArg, new[] { kValue, k }) + : kValue; + iteratorTarget[k] = mappedValue; + } + + return iteratorTarget; + } + + if (source.IsNullOrUndefined()) + { + ExceptionHelper.ThrowTypeError(_realm, "Cannot convert undefined or null to object"); + } + + var arrayLike = TypeConverter.ToObject(_realm, source); + var len = arrayLike.Length; + + var argumentList = new JsValue[] { JsNumber.Create(len) }; + var targetObj = TypedArrayCreate((IConstructor) c, argumentList); + + var mappingArgs = mapping ? new JsValue[2] : null; + for (uint k = 0; k < len; ++k) + { + var Pk = JsNumber.Create(k); + var kValue = arrayLike.Get(Pk); + JsValue mappedValue; + if (mapping) + { + mappingArgs[0] = kValue; + mappingArgs[1] = Pk; + mappedValue = ((ICallable) mapFunction).Call(thisArg, mappingArgs); + } + else + { + mappedValue = kValue; + } + + targetObj.Set(Pk, mappedValue, true); + } + + return targetObj; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.of + /// + private JsValue Of(JsValue thisObj, JsValue[] items) + { + var len = items.Length; + + if (!thisObj.IsConstructor) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var newObj = TypedArrayCreate((IConstructor) thisObj, new JsValue[] { len }); + + var k = 0; + while (k < len) + { + var kValue = items[k]; + newObj[k] = kValue; + k++; + } + + return newObj; + } + + /// + /// https://tc39.es/ecma262/#typedarray-species-create + /// + internal TypedArrayInstance TypedArraySpeciesCreate(TypedArrayInstance exemplar, JsValue[] argumentList) + { + var defaultConstructor = exemplar._arrayElementType.GetConstructor(_realm.Intrinsics); + var constructor = SpeciesConstructor(exemplar, defaultConstructor); + var result = TypedArrayCreate(constructor, argumentList); + if (result._contentType != exemplar._contentType) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + return result; + } + + /// + /// https://tc39.es/ecma262/#typedarray-create + /// + private TypedArrayInstance TypedArrayCreate(IConstructor constructor, JsValue[] argumentList) + { + var newTypedArray = Construct(constructor, argumentList).ValidateTypedArray(_realm); + if (argumentList.Length == 1 && argumentList[0] is JsNumber number) + { + if (newTypedArray.Length < number._value) + { + ExceptionHelper.ThrowTypeError(_realm); + } + } + + return newTypedArray; + } + + private static JsValue Species(JsValue thisObject, JsValue[] arguments) + { + return thisObject; + } + + public override JsValue Call(JsValue thisObject, JsValue[] arguments) + { + ExceptionHelper.ThrowTypeError(_realm, "Abstract class TypedArray not directly constructable"); + return Undefined; + } + + public ObjectInstance Construct(JsValue[] args, JsValue newTarget) + { + ExceptionHelper.ThrowTypeError(_realm, "Abstract class TypedArray not directly constructable"); + return null; + } + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs new file mode 100644 index 0000000000..20958a7523 --- /dev/null +++ b/Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs @@ -0,0 +1,1362 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jint.Collections; +using Jint.Native.Array; +using Jint.Native.ArrayBuffer; +using Jint.Native.Iterator; +using Jint.Native.Number; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Pooling; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; +using Jint.Runtime.Interpreter.Expressions; + +namespace Jint.Native.TypedArray +{ + /// + /// https://tc39.es/ecma262/#sec-properties-of-the-%typedarrayprototype%-object + /// + internal sealed class IntrinsicTypedArrayPrototype : ObjectInstance + { + private readonly Realm _realm; + private readonly IntrinsicTypedArrayConstructor _constructor; + private ClrFunctionInstance _originalIteratorFunction; + + internal IntrinsicTypedArrayPrototype( + Engine engine, + Realm realm, + ObjectInstance objectPrototype, + IntrinsicTypedArrayConstructor constructor) : base(engine) + { + _prototype = objectPrototype; + _realm = realm; + _constructor = constructor; + } + + protected override void Initialize() + { + const PropertyFlag lengthFlags = PropertyFlag.Configurable; + const PropertyFlag propertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable; + var properties = new PropertyDictionary(31, false) + { + ["buffer"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get buffer", Buffer, 0, lengthFlags), Undefined, PropertyFlag.Configurable), + ["byteLength"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get byteLength", ByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable), + ["byteOffset"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(Engine, "get byteOffset", ByteOffset, 0, lengthFlags), Undefined, PropertyFlag.Configurable), + ["length"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(Engine, "get length", GetLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable), + ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), + ["copyWithin"] = new(new ClrFunctionInstance(Engine, "copyWithin", CopyWithin, 2, PropertyFlag.Configurable), propertyFlags), + ["entries"] = new(new ClrFunctionInstance(Engine, "entries", Entries, 0, PropertyFlag.Configurable), propertyFlags), + ["every"] = new(new ClrFunctionInstance(Engine, "every", Every, 1, PropertyFlag.Configurable), propertyFlags), + ["fill"] = new(new ClrFunctionInstance(Engine, "fill", Fill, 1, PropertyFlag.Configurable), propertyFlags), + ["filter"] = new(new ClrFunctionInstance(Engine, "filter", Filter, 1, PropertyFlag.Configurable), propertyFlags), + ["find"] = new(new ClrFunctionInstance(Engine, "find", Find, 1, PropertyFlag.Configurable), propertyFlags), + ["findIndex"] = new(new ClrFunctionInstance(Engine, "findIndex", FindIndex, 1, PropertyFlag.Configurable), propertyFlags), + ["forEach"] = new(new ClrFunctionInstance(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), propertyFlags), + ["includes"] = new(new ClrFunctionInstance(Engine, "includes", Includes, 1, PropertyFlag.Configurable), propertyFlags), + ["indexOf"] = new(new ClrFunctionInstance(Engine, "indexOf", IndexOf, 1, PropertyFlag.Configurable), propertyFlags), + ["join"] = new(new ClrFunctionInstance(Engine, "join", Join, 1, PropertyFlag.Configurable), propertyFlags), + ["keys"] = new(new ClrFunctionInstance(Engine, "keys", Keys, 0, PropertyFlag.Configurable), propertyFlags), + ["lastIndexOf"] = new(new ClrFunctionInstance(Engine, "lastIndexOf", LastIndexOf, 1, PropertyFlag.Configurable), propertyFlags), + ["map"] = new(new ClrFunctionInstance(Engine, "map", Map, 1, PropertyFlag.Configurable), propertyFlags), + ["reduce"] = new(new ClrFunctionInstance(Engine, "reduce", Reduce, 1, PropertyFlag.Configurable), propertyFlags), + ["reduceRight"] = new(new ClrFunctionInstance(Engine, "reduceRight", ReduceRight, 1, PropertyFlag.Configurable), propertyFlags), + ["reverse"] = new(new ClrFunctionInstance(Engine, "reverse", Reverse, 0, PropertyFlag.Configurable), propertyFlags), + ["set"] = new(new ClrFunctionInstance(Engine, "set", Set, 1, PropertyFlag.Configurable), propertyFlags), + ["slice"] = new(new ClrFunctionInstance(Engine, "slice", Slice, 2, PropertyFlag.Configurable), propertyFlags), + ["some"] = new(new ClrFunctionInstance(Engine, "some", Some, 1, PropertyFlag.Configurable), propertyFlags), + ["sort"] = new(new ClrFunctionInstance(Engine, "sort", Sort, 1, PropertyFlag.Configurable), propertyFlags), + ["subarray"] = new(new ClrFunctionInstance(Engine, "subarray", Subarray, 2, PropertyFlag.Configurable), propertyFlags), + ["toLocaleString"] = new(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), propertyFlags), + ["toString"] = new(new ClrFunctionInstance(Engine, "toLocaleString", _realm.Intrinsics.Array.PrototypeObject.ToString, 0, PropertyFlag.Configurable), propertyFlags), + ["values"] = new(new ClrFunctionInstance(Engine, "values", Values, 0, PropertyFlag.Configurable), propertyFlags) + }; + SetProperties(properties); + + _originalIteratorFunction = new ClrFunctionInstance(Engine, "iterator", Values, 1); + var symbols = new SymbolDictionary(2) + { + [GlobalSymbolRegistry.Iterator] = new(_originalIteratorFunction, propertyFlags), + [GlobalSymbolRegistry.ToStringTag] = new GetSetPropertyDescriptor( + new ClrFunctionInstance(Engine, "get [Symbol.toStringTag]", ToStringTag, 0, PropertyFlag.Configurable), + Undefined, + PropertyFlag.Configurable) + }; + SetSymbols(symbols); + } + + /// + /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer + /// + private JsValue Buffer(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj as TypedArrayInstance; + if (o is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + return o._viewedArrayBuffer; + } + + /// + /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength + /// + private JsValue ByteLength(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj as TypedArrayInstance; + if (o is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + if (o._viewedArrayBuffer.IsDetachedBuffer) + { + return JsNumber.PositiveZero; + } + + return JsNumber.Create(o._byteLength); + } + + /// + /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset + /// + private JsValue ByteOffset(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj as TypedArrayInstance; + if (o is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + if (o._viewedArrayBuffer.IsDetachedBuffer) + { + return JsNumber.PositiveZero; + } + + return JsNumber.Create(o._byteOffset); + } + + /// + /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length + /// + private JsValue GetLength(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj as TypedArrayInstance; + if (o is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var buffer = o._viewedArrayBuffer; + if (buffer.IsDetachedBuffer) + { + return JsNumber.PositiveZero; + } + + return JsNumber.Create(o.Length); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin + /// + private JsValue CopyWithin(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + + var target = arguments.At(0); + var start = arguments.At(1); + var end = arguments.At(2); + + long len = o.Length; + + var relativeTarget = TypeConverter.ToIntegerOrInfinity(target); + + long to; + if (double.IsNegativeInfinity(relativeTarget)) + { + to = 0; + } + else if (relativeTarget < 0) + { + to = (long) System.Math.Max(len + relativeTarget, 0); + } + else + { + to = (long) System.Math.Min(relativeTarget, len); + } + + var relativeStart = TypeConverter.ToIntegerOrInfinity(start); + + long from; + if (double.IsNegativeInfinity(relativeStart)) + { + from = 0; + } + else if (relativeStart < 0) + { + from = (long) System.Math.Max(len + relativeStart, 0); + } + else + { + from = (long) System.Math.Min(relativeStart, len); + } + + var relativeEnd = end.IsUndefined() + ? len + : TypeConverter.ToIntegerOrInfinity(end); + + long final; + if (double.IsNegativeInfinity(relativeEnd)) + { + final = 0; + } + else if (relativeEnd < 0) + { + final = (long) System.Math.Max(len + relativeEnd, 0); + } + else + { + final = (long) System.Math.Min(relativeEnd, len); + } + + var count = System.Math.Min(final - from, len - to); + + if (count > 0) + { + var buffer = o._viewedArrayBuffer; + buffer.AssertNotDetached(); + + var elementSize = o._arrayElementType.GetElementSize(); + var byteOffset = o._byteOffset; + var toByteIndex = to * elementSize + byteOffset; + var fromByteIndex = from * elementSize + byteOffset; + var countBytes = count * elementSize; + + int direction; + if (fromByteIndex < toByteIndex && toByteIndex < fromByteIndex + countBytes) + { + direction = -1; + fromByteIndex = fromByteIndex + countBytes - 1; + toByteIndex = toByteIndex + countBytes - 1; + } + else + { + direction = 1; + } + + while (countBytes > 0) + { + var value = buffer.GetValueFromBuffer((int) fromByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); + buffer.SetValueInBuffer((int) toByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered); + fromByteIndex += direction; + toByteIndex += direction; + countBytes--; + } + } + + return o; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries + /// + private JsValue Entries(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + return _realm.Intrinsics.Iterator.CreateArrayLikeIterator(o, ArrayIteratorType.KeyAndValue); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.every + /// + private JsValue Every(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + if (len == 0) + { + return JsBoolean.True; + } + + var predicate = GetCallable(arguments.At(0)); + var thisArg = arguments.At(1); + + var args = _engine._jsValueArrayPool.RentArray(3); + args[2] = o; + for (var k = 0; k < len; k++) + { + args[0] = o[k]; + args[1] = k; + if (!TypeConverter.ToBoolean(predicate.Call(thisArg, args))) + { + return JsBoolean.False; + } + } + + _engine._jsValueArrayPool.ReturnArray(args); + + return JsBoolean.True; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill + /// + private JsValue Fill(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + + var start = arguments.At(1); + var end = arguments.At(2); + + var value = o._contentType == TypedArrayContentType.BigInt + ? TypeConverter.ToBigInt(arguments.At(0)) + : TypeConverter.ToNumber(arguments.At(0)); + + var len = o.Length; + + int k; + var relativeStart = TypeConverter.ToIntegerOrInfinity(start); + if (double.IsNegativeInfinity(relativeStart)) + { + k = 0; + } + else if (relativeStart < 0) + { + k = (int) System.Math.Max(len + relativeStart, 0); + } + else + { + k = (int) System.Math.Min(relativeStart, len); + } + + uint final; + var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); + if (double.IsNegativeInfinity(relativeEnd)) + { + final = 0; + } + else if (relativeEnd < 0) + { + final = (uint) System.Math.Max(len + relativeEnd, 0); + } + else + { + final = (uint) System.Math.Min(relativeEnd, len); + } + + o._viewedArrayBuffer.AssertNotDetached(); + + for (var i = k; i < final; ++i) + { + o[i] = value; + } + + return thisObj; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter + /// + private JsValue Filter(JsValue thisObj, JsValue[] arguments) + { + var callbackfn = GetCallable(arguments.At(0)); + var thisArg = arguments.At(1); + + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + var kept = new List(); + var captured = 0; + + var args = _engine._jsValueArrayPool.RentArray(3); + args[2] = o; + for (var k = 0; k < len; k++) + { + var kValue = o[k]; + args[0] = kValue; + args[1] = k; + var selected = callbackfn.Call(thisArg, args); + if (TypeConverter.ToBoolean(selected)) + { + kept.Add(kValue); + captured++; + } + } + + _engine._jsValueArrayPool.ReturnArray(args); + + var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, new JsValue[] { captured }); + for (var n = 0; n < captured; ++n) + { + a[n] = kept[n]; + } + + return a; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.find + /// + private JsValue Find(JsValue thisObj, JsValue[] arguments) + { + return DoFind(thisObj, arguments).Value; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex + /// + private JsValue FindIndex(JsValue thisObj, JsValue[] arguments) + { + return DoFind(thisObj, arguments).Key; + } + + private KeyValuePair DoFind(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + var predicate = GetCallable(arguments.At(0)); + var thisArg = arguments.At(1); + + var args = _engine._jsValueArrayPool.RentArray(3); + args[2] = o; + for (var k = 0; k < len; k++) + { + var kNumber = JsNumber.Create(k); + var kValue = o[k]; + args[0] = kValue; + args[1] = kNumber; + if (TypeConverter.ToBoolean(predicate.Call(thisArg, args))) + { + return new KeyValuePair(kNumber, kValue); + } + } + + return new KeyValuePair(JsNumber.IntegerNegativeOne, Undefined); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach + /// + private JsValue ForEach(JsValue thisObj, JsValue[] arguments) + { + var callbackfn = GetCallable(arguments.At(0)); + var thisArg = arguments.At(1); + + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + var args = _engine._jsValueArrayPool.RentArray(3); + args[2] = o; + for (var k = 0; k < len; k++) + { + var kValue = o[k]; + args[0] = kValue; + args[1] = k; + callbackfn.Call(thisArg, args); + } + + _engine._jsValueArrayPool.ReturnArray(args); + + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes + /// + private JsValue Includes(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + if (len == 0) + { + return false; + } + + var searchElement = arguments.At(0); + var fromIndex = arguments.At(1, 0); + + var n = TypeConverter.ToIntegerOrInfinity(fromIndex); + if (double.IsPositiveInfinity(n)) + { + return JsBoolean.False; + } + else if (double.IsNegativeInfinity(n)) + { + n = 0; + } + + long k; + if (n >= 0) + { + k = (long) n; + } + else + { + k = (long) (len + n); + if (k < 0) + { + k = 0; + } + } + + while (k < len) + { + var value = o[(int) k]; + if (JintBinaryExpression.SameValueZero(value, searchElement)) + { + return JsBoolean.True; + } + + k++; + } + + return JsBoolean.False; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof + /// + private JsValue IndexOf(JsValue thisObj, JsValue[] arguments) + { + var searchElement = arguments.At(0); + var fromIndex = arguments.At(1); + + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + if (len == 0) + { + return JsNumber.IntegerNegativeOne; + } + + var n = TypeConverter.ToIntegerOrInfinity(fromIndex); + if (double.IsPositiveInfinity(n)) + { + return JsNumber.IntegerNegativeOne; + } + else if (double.IsNegativeInfinity(n)) + { + n = 0; + } + + long k; + if (n >= 0) + { + k = (long) n; + } + else + { + k = (long) (len + n); + if (k < 0) + { + k = 0; + } + } + + for (; k < len; k++) + { + var kPresent = o.HasProperty(k); + if (kPresent) + { + var elementK = o[(int) k]; + var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement); + if (same) + { + return k; + } + } + } + + return JsNumber.IntegerNegativeOne; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.join + /// + private JsValue Join(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + + var separator = arguments.At(0); + var len = o.Length; + + var sep = TypeConverter.ToString(separator.IsUndefined() ? JsString.CommaString : separator); + // as per the spec, this has to be called after ToString(separator) + if (len == 0) + { + return JsString.Empty; + } + + static string StringFromJsValue(JsValue value) + { + return value.IsUndefined() + ? "" + : TypeConverter.ToString(value); + } + + var s = StringFromJsValue(o[0]); + if (len == 1) + { + return s; + } + + using var sb = StringBuilderPool.Rent(); + sb.Builder.Append(s); + for (var k = 1; k < len; k++) + { + sb.Builder.Append(sep); + sb.Builder.Append(StringFromJsValue(o[k])); + } + + return sb.ToString(); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys + /// + private JsValue Keys(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + return _realm.Intrinsics.Iterator.CreateArrayLikeIterator(o, ArrayIteratorType.Key); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof + /// + private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments) + { + var searchElement = arguments.At(0); + + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + if (len == 0) + { + return JsNumber.IntegerNegativeOne; + } + + var fromIndex = arguments.At(1, len - 1); + var n = TypeConverter.ToIntegerOrInfinity(fromIndex); + + if (double.IsNegativeInfinity(n)) + { + return JsNumber.IntegerNegativeOne; + } + + long k; + if (n >= 0) + { + k = (long) System.Math.Min(n, len - 1); + } + else + { + k = (long) (len + n); + } + + for (; k >= 0; k--) + { + var kPresent = o.HasProperty(k); + if (kPresent) + { + var elementK = o[(int) k]; + var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement); + if (same) + { + return k; + } + } + } + + return JsNumber.IntegerNegativeOne; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.map + /// + private ObjectInstance Map(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + var thisArg = arguments.At(1); + var callable = GetCallable(arguments.At(0)); + + var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, new JsValue[] { len }); + var args = _engine._jsValueArrayPool.RentArray(3); + args[2] = o; + for (var k = 0; k < len; k++) + { + args[0] = o[k]; + args[1] = k; + var mappedValue = callable.Call(thisArg, args); + a[k] = mappedValue; + } + + _engine._jsValueArrayPool.ReturnArray(args); + return a; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce + /// + private JsValue Reduce(JsValue thisObj, JsValue[] arguments) + { + var callbackfn = GetCallable(arguments.At(0)); + var initialValue = arguments.At(1); + + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + + if (len == 0 && arguments.Length < 2) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var k = 0; + var accumulator = Undefined; + if (!initialValue.IsUndefined()) + { + accumulator = initialValue; + } + else + { + accumulator = o[k]; + k++; + } + + var args = _engine._jsValueArrayPool.RentArray(4); + args[3] = o; + while (k < len) + { + var kValue = o[k]; + args[0] = accumulator; + args[1] = kValue; + args[2] = k; + accumulator = callbackfn.Call(Undefined, args); + k++; + } + + _engine._jsValueArrayPool.ReturnArray(args); + + return accumulator; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright + /// + private JsValue ReduceRight(JsValue thisObj, JsValue[] arguments) + { + var callbackfn = GetCallable(arguments.At(0)); + var initialValue = arguments.At(1); + + var o = thisObj.ValidateTypedArray(_realm); + var len = (int) o.Length; + + if (len == 0 && arguments.Length < 2) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var k = len - 1; + JsValue accumulator; + if (arguments.Length > 1) + { + accumulator = initialValue; + } + else + { + accumulator = o[k]; + k--; + } + + var jsValues = _engine._jsValueArrayPool.RentArray(4); + jsValues[3] = o; + for (; k >= 0; k--) + { + jsValues[0] = accumulator; + jsValues[1] = o[k]; + jsValues[2] = k; + accumulator = callbackfn.Call(Undefined, jsValues); + } + + _engine._jsValueArrayPool.ReturnArray(jsValues); + return accumulator; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse + /// + private ObjectInstance Reverse(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + var len = (int) o.Length; + var middle = (int) System.Math.Floor(len / 2.0); + var lower = 0; + while (lower != middle) + { + var upper = len - lower - 1; + + var lowerValue = o[lower]; + var upperValue = o[upper]; + + o[lower] = upperValue; + o[upper] = lowerValue; + + lower++; + } + + return o; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.set + /// + private JsValue Set(JsValue thisObj, JsValue[] arguments) + { + var target = thisObj as TypedArrayInstance; + if (target is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var source = arguments.At(0); + var offset = arguments.At(1); + + var targetOffset = TypeConverter.ToIntegerOrInfinity(offset); + if (targetOffset < 0) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid offset"); + } + + if (source is TypedArrayInstance typedArrayInstance) + { + SetTypedArrayFromTypedArray(target, targetOffset, typedArrayInstance); + } + else + { + SetTypedArrayFromArrayLike(target, targetOffset, source); + } + + return Undefined; + } + + /// + /// https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray + /// + private void SetTypedArrayFromTypedArray(TypedArrayInstance target, double targetOffset, TypedArrayInstance source) + { + var targetBuffer = target._viewedArrayBuffer; + targetBuffer.AssertNotDetached(); + + var targetLength = target._arrayLength; + var srcBuffer = source._viewedArrayBuffer; + srcBuffer.AssertNotDetached(); + + var targetType = target._arrayElementType; + var targetElementSize = targetType.GetElementSize(); + var targetByteOffset = target._byteOffset; + + var srcType = source._arrayElementType; + var srcElementSize = srcType.GetElementSize(); + var srcLength = source._arrayLength; + var srcByteOffset = source._byteOffset; + + if (double.IsNegativeInfinity(targetOffset)) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); + } + + if (srcLength + targetOffset > targetLength) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); + } + + if (target._contentType != source._contentType) + { + ExceptionHelper.ThrowTypeError(_realm, "Content type mismatch"); + } + + bool same; + if (srcBuffer.IsSharedArrayBuffer && targetBuffer.IsSharedArrayBuffer) + { + // a. If srcBuffer.[[ArrayBufferData]] and targetBuffer.[[ArrayBufferData]] are the same Shared Data Block values, let same be true; else let same be false. + ExceptionHelper.ThrowNotImplementedException("SharedBuffer not implemented"); + same = false; + } + else + { + same = SameValue(srcBuffer, targetBuffer); + } + + int srcByteIndex; + if (same) + { + var srcByteLength = source._byteLength; + srcBuffer = srcBuffer.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, srcByteLength, _realm.Intrinsics.ArrayBuffer); + // %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. + srcByteIndex = 0; + } + else + { + srcByteIndex = srcByteOffset; + } + + var targetByteIndex = (int) (targetOffset * targetElementSize + targetByteOffset); + var limit = targetByteIndex + targetElementSize * srcLength; + + if (srcType == targetType) + { + // NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + while (targetByteIndex < limit) + { + var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); + targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered); + srcByteIndex += 1; + targetByteIndex += 1; + } + } + else + { + while (targetByteIndex < limit) + { + var value = srcBuffer.GetValueFromBuffer(srcByteIndex, srcType, true, ArrayBufferOrder.Unordered); + targetBuffer.SetValueInBuffer(targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered); + srcByteIndex += srcElementSize; + targetByteIndex += targetElementSize; + } + } + } + + /// + /// https://tc39.es/ecma262/#sec-settypedarrayfromarraylike + /// + private void SetTypedArrayFromArrayLike(TypedArrayInstance target, double targetOffset, JsValue source) + { + var targetBuffer = target._viewedArrayBuffer; + targetBuffer.AssertNotDetached(); + + 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 srcLength = src.GetLength(); + + if (double.IsNegativeInfinity(targetOffset)) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); + } + + if (srcLength + targetOffset > targetLength) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); + } + + var targetByteIndex = targetOffset * targetElementSize + targetByteOffset; + ulong k = 0; + var limit = targetByteIndex + targetElementSize * srcLength; + + while (targetByteIndex < limit) + { + double value; + if (target._contentType == TypedArrayContentType.BigInt) + { + value = TypeConverter.ToBigInt(src.Get(k)); + } + else + { + value = TypeConverter.ToNumber(src.Get(k)); + } + + targetBuffer.AssertNotDetached(); + targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered); + k++; + targetByteIndex += targetElementSize; + } + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice + /// + private JsValue Slice(JsValue thisObj, JsValue[] arguments) + { + var start = arguments.At(0); + var end = arguments.At(1); + + var o = thisObj.ValidateTypedArray(_realm); + long len = o.Length; + + var relativeStart = TypeConverter.ToIntegerOrInfinity(start); + int k; + if (double.IsNegativeInfinity(relativeStart)) + { + k = 0; + } + else if (relativeStart < 0) + { + k = (int) System.Math.Max(len + relativeStart, 0); + } + else + { + k = (int) System.Math.Min(relativeStart, len); + } + + var relativeEnd = end.IsUndefined() + ? len + : TypeConverter.ToIntegerOrInfinity(end); + + long final; + if (double.IsNegativeInfinity(relativeEnd)) + { + final = 0; + } + else if (relativeEnd < 0) + { + final = (long) System.Math.Max(len + relativeEnd, 0); + } + else + { + final = (long) System.Math.Min(relativeEnd, len); + } + + var count = System.Math.Max(final - k, 0); + var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, new JsValue[] { count }); + + if (count > 0) + { + o._viewedArrayBuffer.AssertNotDetached(); + var srcType = o._arrayElementType; + var targetType = a._arrayElementType; + if (srcType != targetType) + { + var n = 0; + while (k < final) + { + var kValue = o[k]; + a[n] = kValue; + k++; + n++; + } + } + else + { + var srcBuffer = o._viewedArrayBuffer; + var targetBuffer = a._viewedArrayBuffer; + var elementSize = srcType.GetElementSize(); + var srcByteOffset = o._byteOffset; + var targetByteIndex = a._byteOffset; + var srcByteIndex = (int) k * elementSize + srcByteOffset; + var limit = targetByteIndex + count * elementSize; + while (targetByteIndex < limit) + { + var value = (JsNumber) srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); + targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value._value, true, ArrayBufferOrder.Unordered); + srcByteIndex++; + targetByteIndex++; + } + } + } + + return a; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.some + /// + private JsValue Some(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + var len = o.Length; + var callbackfn = GetCallable(arguments.At(0)); + var thisArg = arguments.At(1); + + var args = _engine._jsValueArrayPool.RentArray(3); + args[2] = o; + for (var k = 0; k < len; k++) + { + args[0] = o[k]; + args[1] = k; + if (TypeConverter.ToBoolean(callbackfn.Call(thisArg, args))) + { + return JsBoolean.True; + } + } + + _engine._jsValueArrayPool.ReturnArray(args); + return JsBoolean.False; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort + /// + private JsValue Sort(JsValue thisObj, JsValue[] arguments) + { + /* + * %TypedArray%.prototype.sort is a distinct function that, except as described below, + * implements the same requirements as those of Array.prototype.sort as defined in 23.1.3.27. + * The implementation of the %TypedArray%.prototype.sort specification may be optimized with the knowledge that the this value is + * an object that has a fixed length and whose integer-indexed properties are not sparse. + */ + + var obj = thisObj.ValidateTypedArray(_realm); + var buffer = obj._viewedArrayBuffer; + var len = obj.Length; + + var compareArg = arguments.At(0); + ICallable compareFn = null; + if (!compareArg.IsUndefined()) + { + compareFn = GetCallable(compareArg); + } + + if (len <= 1) + { + return obj; + } + + JsValue[] array; + try + { + var comparer = TypedArrayComparer.WithFunction(buffer, compareFn); + var operations = ArrayOperations.For(obj); + array = operations + .OrderBy(x => x, comparer) + .ToArray(); + } + catch (InvalidOperationException e) + { + throw e.InnerException ?? e; + } + + for (var i = 0; i < (uint) array.Length; ++i) + { + obj[i] = array[i]; + } + + return obj; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray + /// + private JsValue Subarray(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj as TypedArrayInstance; + if (o is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + var begin = arguments.At(0); + var end = arguments.At(1); + + var buffer = o._viewedArrayBuffer; + var srcLength = o.Length; + var relativeBegin = TypeConverter.ToIntegerOrInfinity(begin); + + double beginIndex; + if (double.IsNegativeInfinity(relativeBegin)) + { + beginIndex = 0; + } + else if (relativeBegin < 0) + { + beginIndex = System.Math.Max(srcLength + relativeBegin, 0); + } + else + { + beginIndex = System.Math.Min(relativeBegin, srcLength); + } + + double relativeEnd; + if (end.IsUndefined()) + { + relativeEnd = srcLength; + } + else + { + relativeEnd = TypeConverter.ToIntegerOrInfinity(end); + } + + double endIndex; + if (double.IsNegativeInfinity(relativeEnd)) + { + endIndex = 0; + } + else if (relativeEnd < 0) + { + endIndex = System.Math.Max(srcLength + relativeEnd, 0); + } + else + { + endIndex = System.Math.Min(relativeEnd, srcLength); + } + + var newLength = System.Math.Max(endIndex - beginIndex, 0); + var elementSize = o._arrayElementType.GetElementSize(); + var srcByteOffset = o._byteOffset; + var beginByteOffset = srcByteOffset + beginIndex * elementSize; + var argumentsList = new JsValue[] { buffer, beginByteOffset, newLength }; + return _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, argumentsList); + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring + /// + private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments) + { + /* + * %TypedArray%.prototype.toLocaleString is a distinct function that implements the same algorithm as Array.prototype.toLocaleString + * as defined in 23.1.3.29 except that the this value's [[ArrayLength]] internal slot is accessed in place of performing + * a [[Get]] of "length". The implementation of the algorithm may be optimized with the knowledge that the this value is an object + * that has a fixed length and whose integer-indexed properties are not sparse. However, such optimization must not introduce + * any observable changes in the specified behaviour of the algorithm. + */ + + var array = thisObj.ValidateTypedArray(_realm); + var len = array.Length; + const string separator = ","; + if (len == 0) + { + return JsString.Empty; + } + + JsValue r; + if (!array.TryGetValue(0, out var firstElement) || firstElement.IsNull() || firstElement.IsUndefined()) + { + r = JsString.Empty; + } + else + { + var elementObj = TypeConverter.ToObject(_realm, firstElement); + var func = elementObj.Get("toLocaleString", elementObj) as ICallable; + if (func is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + r = func.Call(elementObj, Arguments.Empty); + } + + for (var k = 1; k < len; k++) + { + var s = r + separator; + var elementObj = TypeConverter.ToObject(_realm, array[k]); + var func = elementObj.Get("toLocaleString", elementObj) as ICallable; + if (func is null) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + r = func.Call(elementObj, Arguments.Empty); + + r = s + r; + } + + return r; + } + + /// + /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.values + /// + private JsValue Values(JsValue thisObj, JsValue[] arguments) + { + var o = thisObj.ValidateTypedArray(_realm); + return _realm.Intrinsics.Iterator.CreateArrayLikeIterator(o, ArrayIteratorType.Value); + } + + /// + /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag + /// + private static JsValue ToStringTag(JsValue thisObj, JsValue[] arguments) + { + if (thisObj is not TypedArrayInstance o) + { + return Undefined; + } + + return o._arrayElementType.GetTypedArrayName(); + } + + private sealed class TypedArrayComparer : IComparer + { + public static TypedArrayComparer WithFunction(ArrayBufferInstance buffer, ICallable compare) + { + return new TypedArrayComparer(buffer, compare); + } + + private readonly ArrayBufferInstance _buffer; + private readonly ICallable _compare; + private readonly JsValue[] _comparableArray = new JsValue[2]; + + private TypedArrayComparer(ArrayBufferInstance buffer, ICallable compare) + { + _buffer = buffer; + _compare = compare; + } + + public int Compare(JsValue x, JsValue y) + { + if (_compare is not null) + { + _comparableArray[0] = x; + _comparableArray[1] = y; + + var v = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray)); + _buffer.AssertNotDetached(); + + if (double.IsNaN(v)) + { + return 0; + } + + return (int) v; + } + + var xValue = x.AsNumber(); + var yValue = y.AsNumber(); + + if (double.IsNaN(xValue) && double.IsNaN(yValue)) + { + return 0; + } + + if (double.IsNaN(xValue)) + { + return 1; + } + + if (double.IsNaN(yValue)) + { + return -1; + } + + if (xValue < yValue) + { + return -1; + } + + if (xValue > yValue) + { + return 1; + } + + if (NumberInstance.IsNegativeZero(xValue) && yValue == 0) + { + return -1; + } + + if (xValue == 0 && NumberInstance.IsNegativeZero(yValue)) + { + return 1; + } + + return 0; + } + } + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/TypeArrayHelper.cs b/Jint/Native/TypedArray/TypeArrayHelper.cs new file mode 100644 index 0000000000..147f5d0a80 --- /dev/null +++ b/Jint/Native/TypedArray/TypeArrayHelper.cs @@ -0,0 +1,21 @@ +using Jint.Runtime; + +namespace Jint.Native.TypedArray +{ + internal static class TypeArrayHelper + { + internal static TypedArrayInstance ValidateTypedArray(this JsValue o, Realm realm) + { + var typedArrayInstance = o as TypedArrayInstance; + if (typedArrayInstance is null) + { + ExceptionHelper.ThrowTypeError(realm); + } + + var buffer = typedArrayInstance._viewedArrayBuffer; + buffer.AssertNotDetached(); + + return typedArrayInstance; + } + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs b/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs new file mode 100644 index 0000000000..f3c53eaf66 --- /dev/null +++ b/Jint/Native/TypedArray/TypedArrayConstructor.Types.cs @@ -0,0 +1,202 @@ +using Jint.Runtime; + +namespace Jint.Native.TypedArray +{ + public sealed class Int8ArrayConstructor : TypedArrayConstructor + { + internal Int8ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int8) + { + } + + public TypedArrayInstance Construct(sbyte[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Uint8ArrayConstructor : TypedArrayConstructor + { + internal Uint8ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint8) + { + } + + public TypedArrayInstance Construct(byte[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Uint8ClampedArrayConstructor : TypedArrayConstructor + { + internal Uint8ClampedArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint8C) + { + } + + public TypedArrayInstance Construct(byte[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Int16ArrayConstructor : TypedArrayConstructor + { + internal Int16ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int16) + { + } + + public TypedArrayInstance Construct(short[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Uint16ArrayConstructor : TypedArrayConstructor + { + internal Uint16ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint16) + { + } + + public TypedArrayInstance Construct(ushort[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Int32ArrayConstructor : TypedArrayConstructor + { + internal Int32ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Int32) + { + } + + public TypedArrayInstance Construct(int[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Uint32ArrayConstructor : TypedArrayConstructor + { + internal Uint32ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Uint32) + { + } + + public TypedArrayInstance Construct(uint[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Float32ArrayConstructor : TypedArrayConstructor + { + internal Float32ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float32) + { + } + + public TypedArrayInstance Construct(float[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class Float64ArrayConstructor : TypedArrayConstructor + { + internal Float64ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.Float64) + { + } + + public TypedArrayInstance Construct(double[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class BigInt64ArrayConstructor : TypedArrayConstructor + { + internal BigInt64ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.BigInt64) + { + } + + public TypedArrayInstance Construct(long[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } + + public sealed class BigUint64ArrayConstructor : TypedArrayConstructor + { + internal BigUint64ArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype) : base(engine, realm, functionPrototype, objectPrototype, TypedArrayElementType.BigUint64) + { + } + + public TypedArrayInstance Construct(ulong[] values) + { + var array = (TypedArrayInstance) base.Construct(new JsValue[] { values.Length }, this); + FillTypedArrayInstance(array, values); + return array; + } + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.cs b/Jint/Native/TypedArray/TypedArrayConstructor.cs new file mode 100644 index 0000000000..eeac9b8315 --- /dev/null +++ b/Jint/Native/TypedArray/TypedArrayConstructor.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using Jint.Collections; +using Jint.Native.Array; +using Jint.Native.ArrayBuffer; +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Native.Symbol; +using Jint.Runtime; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.TypedArray +{ + /// + /// https://tc39.es/ecma262/#sec-typedarray-constructors + /// + public abstract class TypedArrayConstructor : FunctionInstance, IConstructor + { + private readonly TypedArrayElementType _arrayElementType; + + internal TypedArrayConstructor( + Engine engine, + Realm realm, + IntrinsicTypedArrayConstructor functionPrototype, + IntrinsicTypedArrayPrototype objectPrototype, + TypedArrayElementType type) : base(engine, realm, new JsString(type.GetTypedArrayName())) + { + _arrayElementType = type; + + _prototype = functionPrototype; + PrototypeObject = new TypedArrayPrototype(engine, objectPrototype, this, type); + _length = new PropertyDescriptor(JsNumber.PositiveThree, PropertyFlag.Configurable); + _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); + } + + public TypedArrayPrototype PrototypeObject { get; } + + protected override void Initialize() + { + var properties = new PropertyDictionary(1, false) + { + ["BYTES_PER_ELEMENT"] = new(new PropertyDescriptor(JsNumber.Create(_arrayElementType.GetElementSize()), PropertyFlag.AllForbidden)) + }; + SetProperties(properties); + } + + public override JsValue Call(JsValue thisObject, JsValue[] arguments) + { + ExceptionHelper.ThrowTypeError(_realm, "Abstract class TypedArray not directly constructable"); + return Undefined; + } + + public ObjectInstance Construct(JsValue[] args, JsValue newTarget) + { + if (newTarget.IsUndefined()) + { + ExceptionHelper.ThrowTypeError(_realm); + } + + Func proto = _arrayElementType switch + { + TypedArrayElementType.Float32 => static intrinsics => intrinsics.Float32Array.PrototypeObject, + TypedArrayElementType.Int8 => static intrinsics => intrinsics.Int8Array.PrototypeObject, + TypedArrayElementType.Int16 => static intrinsics => intrinsics.Int16Array.PrototypeObject, + TypedArrayElementType.Int32 => static intrinsics => intrinsics.Int32Array.PrototypeObject, + TypedArrayElementType.BigInt64 => static intrinsics => intrinsics.BigInt64Array.PrototypeObject, + TypedArrayElementType.Float64 => static intrinsics => intrinsics.Float64Array.PrototypeObject, + TypedArrayElementType.Uint8 => static intrinsics => intrinsics.Uint8Array.PrototypeObject, + TypedArrayElementType.Uint8C => static intrinsics => intrinsics.Uint8ClampedArray.PrototypeObject, + TypedArrayElementType.Uint16 => static intrinsics => intrinsics.Uint16Array.PrototypeObject, + TypedArrayElementType.Uint32 => static intrinsics => intrinsics.Uint32Array.PrototypeObject, + TypedArrayElementType.BigUint64 => static intrinsics => intrinsics.BigUint64Array.PrototypeObject, + _ => null + }; + + var numberOfArgs = args.Length; + if (numberOfArgs == 0) + { + return AllocateTypedArray(newTarget, proto, 0); + } + + var firstArgument = args[0]; + if (firstArgument.IsObject()) + { + var o = AllocateTypedArray(newTarget, proto); + if (firstArgument is TypedArrayInstance typedArrayInstance) + { + InitializeTypedArrayFromTypedArray(o, typedArrayInstance); + } + else if (firstArgument is ArrayBufferInstance arrayBuffer) + { + var byteOffset = numberOfArgs > 1 ? args[1] : Undefined; + var length = numberOfArgs > 2 ? args[2] : Undefined; + InitializeTypedArrayFromArrayBuffer(o, arrayBuffer, byteOffset, length); + } + else + { + var usingIterator = GetMethod(_realm, firstArgument, GlobalSymbolRegistry.Iterator); + if (usingIterator is not null) + { + var values = IterableToList(_realm, firstArgument, usingIterator); + InitializeTypedArrayFromList(o, values); + } + else + { + InitializeTypedArrayFromArrayLike(o, (ObjectInstance) firstArgument); + } + } + + return o; + } + + var elementLength = TypeConverter.ToIndex(_realm, firstArgument); + return AllocateTypedArray(newTarget, proto, elementLength); + } + + /// + /// https://tc39.es/ecma262/#sec-iterabletolist + /// + internal static List IterableToList(Realm realm, JsValue items, ICallable usingIterator) + { + var iteratorRecord = items.GetIterator(realm); + var values = new List(); + while (iteratorRecord.TryIteratorStep(out var nextItem)) + { + values.Add(nextItem.Get(CommonProperties.Value)); + } + + return values; + } + + /// + /// https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray + /// + private void InitializeTypedArrayFromTypedArray(TypedArrayInstance o, TypedArrayInstance srcArray) + { + var srcData = srcArray._viewedArrayBuffer; + srcData.AssertNotDetached(); + + var elementType = o._arrayElementType; + var elementLength = srcArray._arrayLength; + var srcType = srcArray._arrayElementType; + var srcElementSize = srcType.GetElementSize(); + var srcByteOffset = srcArray._byteOffset; + var elementSize = elementType.GetElementSize(); + var byteLength = elementSize * elementLength; + + var bufferConstructor = (JsValue) (!srcData.IsSharedArrayBuffer + ? SpeciesConstructor(srcData, _realm.Intrinsics.ArrayBuffer) + : _realm.Intrinsics.ArrayBuffer); + + ArrayBufferInstance data; + if (elementType == srcType) + { + data = srcData.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, byteLength, bufferConstructor); + } + else + { + data = _realm.Intrinsics.ArrayBuffer.AllocateArrayBuffer(bufferConstructor, byteLength); + srcData.AssertNotDetached(); + if (srcArray._contentType != o._contentType) + { + ExceptionHelper.ThrowTypeError(_realm, "Content types differ"); + } + + var srcByteIndex = srcByteOffset; + var targetByteIndex = 0; + var count = elementLength; + while (count > 0) + { + var value = srcData.GetValueFromBuffer(srcByteIndex, srcType, true, ArrayBufferOrder.Unordered); + data.SetValueInBuffer(targetByteIndex, elementType, value, true, ArrayBufferOrder.Unordered); + srcByteIndex += srcElementSize; + targetByteIndex += elementSize; + count--; + } + } + + o._viewedArrayBuffer = data; + o._arrayLength = elementLength; + o._byteLength = byteLength; + o._byteOffset = 0; + } + + /// + /// https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer + /// + private void InitializeTypedArrayFromArrayBuffer( + TypedArrayInstance o, + ArrayBufferInstance buffer, + JsValue byteOffset, + JsValue length) + { + var elementSize = o._arrayElementType.GetElementSize(); + var offset = (int) TypeConverter.ToIndex(_realm, byteOffset); + if (offset % elementSize != 0) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid offset"); + } + + int newByteLength; + var newLength = 0; + if (!length.IsUndefined()) + { + newLength = (int) TypeConverter.ToIndex(_realm, length); + } + + buffer.AssertNotDetached(); + + var bufferByteLength = buffer.ArrayBufferByteLength; + if (length.IsUndefined()) + { + if (bufferByteLength % elementSize != 0) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length"); + } + + newByteLength = bufferByteLength - offset; + if (newByteLength < 0) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length"); + } + } + else + { + newByteLength = newLength * elementSize; + if (offset + newByteLength > bufferByteLength) + { + ExceptionHelper.ThrowRangeError(_realm, "Invalid buffer byte length"); + } + } + + o._viewedArrayBuffer = buffer; + o._arrayLength = (uint) (newByteLength / elementSize); + o._byteLength = (uint) newByteLength; + o._byteOffset = offset; + } + + private static void InitializeTypedArrayFromList(TypedArrayInstance o, List values) + { + var len = values.Count; + o.AllocateTypedArrayBuffer((uint) len); + for (var k = 0; k < len; ++k) + { + o[k] = values[k]; + } + } + + /// + /// https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike + /// + private static void InitializeTypedArrayFromArrayLike(TypedArrayInstance o, ObjectInstance arrayLike) + { + var operations = ArrayOperations.For(arrayLike); + var len = operations.GetLongLength(); + o.AllocateTypedArrayBuffer(len); + for (uint k = 0; k < len; ++k) + { + o[(int) k] = operations.Get(k); + } + } + + /// + /// https://tc39.es/ecma262/#sec-allocatetypedarray + /// + private TypedArrayInstance AllocateTypedArray(JsValue newTarget, Func defaultProto, uint length = 0) + { + var proto = GetPrototypeFromConstructor(newTarget, defaultProto); + var realm = GetFunctionRealm(newTarget); + var obj = new TypedArrayInstance(_engine, realm.Intrinsics, _arrayElementType, length) + { + _prototype = proto + }; + if (length > 0) + { + obj.AllocateTypedArrayBuffer(length); + } + + return obj; + } + + internal static void FillTypedArrayInstance(TypedArrayInstance target, System.Array values) + { + for (var i = 0; i < values.Length; ++i) + { + target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values.GetValue(i))); + } + } + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/TypedArrayContentType.cs b/Jint/Native/TypedArray/TypedArrayContentType.cs new file mode 100644 index 0000000000..fdfe2b65be --- /dev/null +++ b/Jint/Native/TypedArray/TypedArrayContentType.cs @@ -0,0 +1,8 @@ +namespace Jint.Native.TypedArray +{ + internal enum TypedArrayContentType : byte + { + Number, + BigInt + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/TypedArrayElementType.cs b/Jint/Native/TypedArray/TypedArrayElementType.cs index 5914721426..54e795bf83 100644 --- a/Jint/Native/TypedArray/TypedArrayElementType.cs +++ b/Jint/Native/TypedArray/TypedArrayElementType.cs @@ -1,6 +1,8 @@ +using Jint.Runtime; + namespace Jint.Native.TypedArray { - internal enum TypedArrayElementType + internal enum TypedArrayElementType : byte { // we have signed first to make comparison vaster to check if signed or unsigned type Int8, @@ -19,7 +21,7 @@ internal enum TypedArrayElementType internal static class TypedArrayExtensions { - internal static int GetElementSize(this TypedArrayElementType type) + internal static byte GetElementSize(this TypedArrayElementType type) { return type switch { @@ -34,7 +36,45 @@ internal static int GetElementSize(this TypedArrayElementType type) TypedArrayElementType.BigUint64 => 8, TypedArrayElementType.Float32 => 4, TypedArrayElementType.Float64 => 8, - _ => -1 + _ => 0 + }; + } + + internal static string GetTypedArrayName(this TypedArrayElementType type) + { + return type switch + { + TypedArrayElementType.Int8 => "Int8Array", + TypedArrayElementType.Uint8 => "Uint8Array", + TypedArrayElementType.Uint8C => "Uint8ClampedArray", + TypedArrayElementType.Int16 => "Int16Array", + TypedArrayElementType.Uint16 => "Uint16Array", + TypedArrayElementType.Int32 => "Int32Array", + TypedArrayElementType.Uint32 => "Uint32Array", + TypedArrayElementType.BigInt64 => "BigInt64Array", + TypedArrayElementType.BigUint64 => "BigUint64Array", + TypedArrayElementType.Float32 => "Float32Array", + TypedArrayElementType.Float64 => "Float64Array", + _ => null + }; + } + + internal static IConstructor GetConstructor(this TypedArrayElementType type, Intrinsics intrinsics) + { + return type switch + { + TypedArrayElementType.Int8 => intrinsics.Int8Array, + TypedArrayElementType.Uint8 => intrinsics.Uint8Array, + TypedArrayElementType.Uint8C => intrinsics.Uint8ClampedArray, + TypedArrayElementType.Int16 => intrinsics.Int16Array, + TypedArrayElementType.Uint16 => intrinsics.Uint16Array, + TypedArrayElementType.Int32 => intrinsics.Int32Array, + TypedArrayElementType.Uint32 => intrinsics.Uint32Array, + TypedArrayElementType.BigInt64 => intrinsics.BigInt64Array, + TypedArrayElementType.BigUint64 => intrinsics.BigUint64Array, + TypedArrayElementType.Float32 => intrinsics.Float32Array, + TypedArrayElementType.Float64 => intrinsics.Float64Array, + _ => null }; } @@ -43,6 +83,11 @@ internal static bool IsUnsignedElementType(this TypedArrayElementType type) return type > TypedArrayElementType.Float64; } + internal static bool FitsInt32(this TypedArrayElementType type) + { + return type <= TypedArrayElementType.Int32; + } + internal static bool IsBigIntElementType(this TypedArrayElementType type) { return type is TypedArrayElementType.BigUint64 or TypedArrayElementType.BigInt64; diff --git a/Jint/Native/TypedArray/TypedArrayInstance.cs b/Jint/Native/TypedArray/TypedArrayInstance.cs new file mode 100644 index 0000000000..6eed65c42f --- /dev/null +++ b/Jint/Native/TypedArray/TypedArrayInstance.cs @@ -0,0 +1,334 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Jint.Native.ArrayBuffer; +using Jint.Native.Number; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.TypedArray +{ + public sealed class TypedArrayInstance : ObjectInstance + { + internal TypedArrayContentType _contentType; + internal readonly TypedArrayElementType _arrayElementType; + internal ArrayBufferInstance _viewedArrayBuffer; + internal uint _byteLength; + internal int _byteOffset; + private readonly Intrinsics _intrinsics; + internal uint _arrayLength; + + private TypedArrayInstance( + Engine engine, + Intrinsics intrinsics) : base(engine) + { + _intrinsics = intrinsics; + _viewedArrayBuffer = new ArrayBufferInstance(engine, 0); + } + + internal TypedArrayInstance( + Engine engine, + Intrinsics intrinsics, + TypedArrayElementType type, + uint length) : this(engine, intrinsics) + { + _arrayElementType = type; + _arrayLength = length; + } + + internal JsValue this[int index] + { + get => IntegerIndexedElementGet(index); + set => IntegerIndexedElementSet(index, value); + } + + public override uint Length => _viewedArrayBuffer.IsDetachedBuffer ? 0 : _arrayLength; + + internal override bool IsIntegerIndexedArray => true; + + /// + /// https://tc39.es/ecma262/#sec-allocatetypedarraybuffer + /// + internal void AllocateTypedArrayBuffer(ulong len) + { + var elementSize = _arrayElementType.GetElementSize(); + var byteLength = elementSize * len; + + var data = _intrinsics.ArrayBuffer.AllocateArrayBuffer(_intrinsics.ArrayBuffer, byteLength); + + _byteLength = (uint) byteLength; + _arrayLength = (uint) len; + _viewedArrayBuffer = data; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasProperty(long numericIndex) + { + return IsValidIntegerIndex(numericIndex); + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p + /// + public override bool HasProperty(JsValue property) + { + if (property.IsString()) + { + var numericIndex = TypeConverter.CanonicalNumericIndexString(property); + if (numericIndex is not null) + { + return IsValidIntegerIndex(numericIndex.Value); + } + } + + return base.HasProperty(property); + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p + /// + public override PropertyDescriptor GetOwnProperty(JsValue property) + { + var numericIndex = TypeConverter.CanonicalNumericIndexString(property); + if (numericIndex is not null) + { + var value = IntegerIndexedElementGet(numericIndex.Value); + if (value.IsUndefined()) + { + return PropertyDescriptor.Undefined; + } + + return new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable); + } + + return base.GetOwnProperty(property); + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver + /// + public override JsValue Get(JsValue property, JsValue receiver) + { + var numericIndex = TypeConverter.CanonicalNumericIndexString(property); + if (numericIndex is not null) + { + return IntegerIndexedElementGet(numericIndex.Value); + } + + return base.Get(property, receiver); + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver + /// + public override bool Set(JsValue property, JsValue value, JsValue receiver) + { + var numericIndex = TypeConverter.CanonicalNumericIndexString(property); + if (numericIndex is not null) + { + IntegerIndexedElementSet(numericIndex.Value, value); + return true; + } + + return base.Set(property, value, receiver); + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc + /// + public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) + { + if (property.IsNumber() || property.IsString()) + { + var numericIndex = TypeConverter.CanonicalNumericIndexString(property); + if (numericIndex is not null) + { + if (!IsValidIntegerIndex(numericIndex.Value)) + { + return false; + } + + if (desc.ConfigurableSet && !desc.Configurable) + { + return false; + } + + if (desc.EnumerableSet && !desc.Enumerable) + { + return false; + } + + if (desc.IsAccessorDescriptor()) + { + return false; + } + + if (desc.WritableSet && !desc.Writable) + { + return false; + } + + IntegerIndexedElementSet(numericIndex.Value, desc.Value); + return true; + } + } + + return base.DefineOwnProperty(property, desc); + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys + /// + public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol) + { + var keys = new List(); + if (!_viewedArrayBuffer.IsDetachedBuffer) + { + var length = Length; + for (uint i = 0; i < length; ++i) + { + keys.Add(JsString.Create(i)); + } + } + + if (_properties is not null) + { + foreach (var pair in _properties) + { + keys.Add(pair.Key.Name); + } + } + + if (_symbols is not null) + { + foreach (var pair in _symbols) + { + keys.Add(pair.Key); + } + } + + return keys; + } + + /// + /// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p + /// + public override bool Delete(JsValue property) + { + var numericIndex = TypeConverter.CanonicalNumericIndexString(property); + if (numericIndex is not null) + { + return !IsValidIntegerIndex(numericIndex.Value); + } + + return base.Delete(property); + } + + // helper to prevent floating points + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JsValue IntegerIndexedElementGet(int index) + { + if (!IsValidIntegerIndex(index)) + { + return Undefined; + } + + return DoIntegerIndexedElementGet(index); + } + + /// + /// https://tc39.es/ecma262/#sec-integerindexedelementget + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JsValue IntegerIndexedElementGet(double index) + { + if (!IsValidIntegerIndex(index)) + { + return Undefined; + } + + return DoIntegerIndexedElementGet((int) index); + } + + private JsValue DoIntegerIndexedElementGet(int index) + { + var offset = _byteOffset; + var elementType = _arrayElementType; + var elementSize = elementType.GetElementSize(); + var indexedPosition = index * elementSize + offset; + var value = _viewedArrayBuffer.GetValueFromBuffer(indexedPosition, elementType, true, ArrayBufferOrder.Unordered); + return _arrayElementType.FitsInt32() ? JsNumber.Create((int) value) : JsNumber.Create(value); + } + + // helper tot prevent floating point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void IntegerIndexedElementSet(int index, JsValue value) + { + var numValue = _contentType == TypedArrayContentType.BigInt + ? TypeConverter.ToBigInt(value) + : TypeConverter.ToNumber(value); + + if (IsValidIntegerIndex(index)) + { + DoIntegerIndexedElementSet(index, numValue); + } + } + + /// + /// https://tc39.es/ecma262/#sec-integerindexedelementset + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void IntegerIndexedElementSet(double index, JsValue value) + { + var numValue = _contentType == TypedArrayContentType.BigInt + ? TypeConverter.ToBigInt(value) + : TypeConverter.ToNumber(value); + + if (IsValidIntegerIndex(index)) + { + DoIntegerIndexedElementSet((int) index, numValue); + } + } + + internal void DoIntegerIndexedElementSet(int index, double numValue) + { + var offset = _byteOffset; + var elementType = _arrayElementType; + var elementSize = elementType.GetElementSize(); + var indexedPosition = index * elementSize + offset; + _viewedArrayBuffer.SetValueInBuffer(indexedPosition, elementType, numValue, true, ArrayBufferOrder.Unordered); + } + + /// + /// https://tc39.es/ecma262/#sec-isvalidintegerindex + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsValidIntegerIndex(double index) + { + return !_viewedArrayBuffer.IsDetachedBuffer + && IsIntegralNumber(index) + && !NumberInstance.IsNegativeZero(index) + && (uint) index < _arrayLength; + } + + /// + /// https://tc39.es/ecma262/#sec-isvalidintegerindex + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsValidIntegerIndex(int index) + { + return !_viewedArrayBuffer.IsDetachedBuffer && (uint) index < _arrayLength; + } + + /// + /// https://tc39.es/ecma262/#sec-isintegralnumber + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsIntegralNumber(double value) + { + return !double.IsNaN(value) + && !double.IsInfinity(value) + && System.Math.Floor(System.Math.Abs(value)) == System.Math.Abs(value); + } + } +} \ No newline at end of file diff --git a/Jint/Native/TypedArray/TypedArrayPrototype.cs b/Jint/Native/TypedArray/TypedArrayPrototype.cs new file mode 100644 index 0000000000..6c4b1af7a3 --- /dev/null +++ b/Jint/Native/TypedArray/TypedArrayPrototype.cs @@ -0,0 +1,36 @@ +using Jint.Collections; +using Jint.Native.Object; +using Jint.Runtime.Descriptors; + +namespace Jint.Native.TypedArray +{ + /// + /// https://tc39.es/ecma262/#sec-properties-of-typedarray-prototype-objects + /// + public sealed class TypedArrayPrototype : ObjectInstance + { + private readonly TypedArrayConstructor _constructor; + private readonly TypedArrayElementType _arrayElementType; + + internal TypedArrayPrototype( + Engine engine, + IntrinsicTypedArrayPrototype objectPrototype, + TypedArrayConstructor constructor, + TypedArrayElementType type) : base(engine) + { + _prototype = objectPrototype; + _constructor = constructor; + _arrayElementType = type; + } + + protected override void Initialize() + { + var properties = new PropertyDictionary(2, false) + { + ["BYTES_PER_ELEMENT"] = new(JsNumber.Create(_arrayElementType.GetElementSize()), PropertyFlag.AllForbidden), + ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable) + }; + SetProperties(properties); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs index c61dd5874f..9a7a6d89c0 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs @@ -37,12 +37,10 @@ protected override void Initialize() protected override object EvaluateInternal() { var a = _engine.Realm.Intrinsics.Array.ConstructFast(_hasSpreads ? 0 : (uint) _expressions.Length); - var expressions = _expressions; uint arrayIndexCounter = 0; - for (uint i = 0; i < (uint) expressions.Length; i++) + foreach (var expr in _expressions) { - var expr = expressions[i]; if (expr == null) { arrayIndexCounter++; diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index 4c9c584462..ba4ccb77c5 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -160,6 +160,11 @@ public override JsValue GetValue() return (JsValue) EvaluateInternal(); } + public static bool SameValueZero(JsValue x, JsValue y) + { + return x == y || (x is JsNumber xNum && y is JsNumber yNum && double.IsNaN(xNum._value) && double.IsNaN(yNum._value)); + } + public static bool StrictlyEqual(JsValue x, JsValue y) { var typeX = x._type & ~InternalTypes.InternalFlags; diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index 9555512a9e..548ad7d5b7 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -1,5 +1,6 @@ using Esprima.Ast; using Jint.Native; +using Jint.Native.Argument; using Jint.Runtime.Environments; namespace Jint.Runtime.Interpreter.Expressions @@ -37,30 +38,41 @@ public override JsValue GetValue() // need to notify correct node when taking shortcut _engine._lastSyntaxNode = _expression; - if (!(_calculatedValue is null)) + if (_calculatedValue is not null) { return _calculatedValue; } var strict = StrictModeScope.IsStrictModeCode; var env = _engine.ExecutionContext.LexicalEnvironment; + + JsValue value; if (JintEnvironment.TryGetIdentifierEnvironmentWithBindingValue( _engine, env, _expressionName, strict, out _, - out var value)) + out value)) { if (value is null) { ExceptionHelper.ThrowReferenceError(_engine.Realm, _expressionName.Key.Name + " has not been initialized"); } - return value; + } + else + { + var reference = _engine._referencePool.Rent(JsValue.Undefined, _expressionName.StringValue, strict, thisValue: null); + value = _engine.GetValue(reference, true); + } + + // make sure arguments access freezes state + if (value is ArgumentsInstance argumentsInstance) + { + argumentsInstance.Materialize(); } - var reference = _engine._referencePool.Rent(JsValue.Undefined, _expressionName.StringValue, strict, thisValue: null); - return _engine.GetValue(reference, true); + return value; } } } \ No newline at end of file diff --git a/Jint/Runtime/Intrinsics.cs b/Jint/Runtime/Intrinsics.cs index d29bc47e9b..194f2859dc 100644 --- a/Jint/Runtime/Intrinsics.cs +++ b/Jint/Runtime/Intrinsics.cs @@ -19,6 +19,7 @@ using Jint.Native.Set; using Jint.Native.String; using Jint.Native.Symbol; +using Jint.Native.TypedArray; using Jint.Native.WeakMap; using Jint.Native.WeakSet; @@ -66,6 +67,19 @@ public sealed class Intrinsics private ArrayBufferConstructor _arrayBufferConstructor; private DataViewConstructor _dataView; + private IntrinsicTypedArrayConstructor _typedArray; + private Int8ArrayConstructor _int8Array; + private Uint8ArrayConstructor _uint8Array; + private Uint8ClampedArrayConstructor _uint8ClampedArray; + private Int16ArrayConstructor _int16Array; + private Uint16ArrayConstructor _uint16Array; + private Int32ArrayConstructor _int32Array; + private Uint32ArrayConstructor _uint32Array; + private BigInt64ArrayConstructor _bigInt64Array; + private BigUint64ArrayConstructor _bigUint64Array; + private Float32ArrayConstructor _float32Array; + private Float64ArrayConstructor _float64Array; + internal Intrinsics(Engine engine, Realm realm) { _engine = engine; @@ -94,6 +108,42 @@ internal Intrinsics(Engine engine, Realm realm) public ArrayBufferConstructor ArrayBuffer => _arrayBufferConstructor ??= new ArrayBufferConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject); + internal IntrinsicTypedArrayConstructor TypedArray => + _typedArray ??= new IntrinsicTypedArrayConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject, "TypedArray"); + + public Int8ArrayConstructor Int8Array => + _int8Array ??= new Int8ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Uint8ArrayConstructor Uint8Array => + _uint8Array ??= new Uint8ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Uint8ClampedArrayConstructor Uint8ClampedArray => + _uint8ClampedArray ??= new Uint8ClampedArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Int16ArrayConstructor Int16Array => + _int16Array ??= new Int16ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Uint16ArrayConstructor Uint16Array => + _uint16Array ??= new Uint16ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Int32ArrayConstructor Int32Array => + _int32Array ??= new Int32ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Uint32ArrayConstructor Uint32Array => + _uint32Array ??= new Uint32ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public BigInt64ArrayConstructor BigInt64Array => + _bigInt64Array ??= new BigInt64ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public BigUint64ArrayConstructor BigUint64Array => + _bigUint64Array ??= new BigUint64ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Float32ArrayConstructor Float32Array => + _float32Array ??= new Float32ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + + public Float64ArrayConstructor Float64Array => + _float64Array ??= new Float64ArrayConstructor(_engine, _realm, TypedArray, TypedArray.PrototypeObject); + public MapConstructor Map => _map ??= new MapConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject); diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index 810be8f63e..188a22a09f 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -493,17 +493,22 @@ internal static double ToUint8(JsValue o) /// /// https://tc39.es/ecma262/#sec-touint8clamp /// - internal static double ToUint8Clamp(JsValue o) + internal static byte ToUint8Clamp(JsValue o) { if (o._type == InternalTypes.Integer) { var intValue = o.AsInteger(); if (intValue is > -1 and < 256) { - return intValue; + return (byte) intValue; } } + return ToUint8ClampUnlikely(o); + } + + private static byte ToUint8ClampUnlikely(JsValue o) + { var number = ToNumber(o); if (double.IsNaN(number)) { @@ -523,20 +528,29 @@ internal static double ToUint8Clamp(JsValue o) var f = Math.Floor(number); if (f + 0.5 < number) { - return f + 1; + return (byte) (f + 1); } if (number < f + 0.5) { - return f; + return (byte) f; } if (f % 2 != 0) { - return f + 1; + return (byte) (f + 1); } - return f; + return (byte) f; + } + + /// + /// https://tc39.es/ecma262/#sec-tobigint + /// + public static double ToBigInt(JsValue value) + { + ExceptionHelper.ThrowNotImplementedException("BigInt not implemented"); + return 0; } /// @@ -557,6 +571,36 @@ internal static double ToBigUint64(JsValue value) return 0; } + + /// + /// https://tc39.es/ecma262/#sec-canonicalnumericindexstring + /// + internal static double? CanonicalNumericIndexString(JsValue value) + { + if (value is JsNumber jsNumber) + { + return jsNumber._value; + } + + if (value is JsString jsString) + { + if (jsString.ToString() == "-0") + { + return -0d; + } + + var n = ToNumber(value); + if (!JsValue.SameValue(ToString(n), value)) + { + return null; + } + + return n; + } + + return null; + } + /// /// https://tc39.es/ecma262/#sec-toindex /// diff --git a/README.md b/README.md index 0041c984a8..2927dbd3df 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ The entire execution engine was rebuild with performance in mind, in many cases - ✔ Reflect - ✔ Symbols - ❌ Tail calls -- ❌ Typed arrays +- ✔ Typed arrays - ❌ Unicode - ✔ Weakmap and Weakset