From fa46507d208970cc368120c09c8420f2199c0ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 29 Mar 2024 15:56:51 +0100 Subject: [PATCH] implement `TypedArray.prototype.with` --- include/hermes/VM/NativeFunctions.def | 1 + lib/VM/JSLib/TypedArray.cpp | 127 ++++++++++++++++++++++++++ test/hermes/TypedArray.js | 15 +++ 3 files changed, 143 insertions(+) diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index 3e605b6a5df..2304e791c01 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -392,6 +392,7 @@ NATIVE_FUNCTION(typedArrayPrototypeSort) NATIVE_FUNCTION(typedArrayPrototypeSubarray) NATIVE_FUNCTION(typedArrayPrototypeSymbolToStringTag) NATIVE_FUNCTION(typedArrayPrototypeToLocaleString) +NATIVE_FUNCTION(typedArrayPrototypeWith) NATIVE_FUNCTION(unescape) NATIVE_FUNCTION(weakMapConstructor) NATIVE_FUNCTION(weakMapPrototypeDelete) diff --git a/lib/VM/JSLib/TypedArray.cpp b/lib/VM/JSLib/TypedArray.cpp index f491f5bf2b7..f37053d0ba0 100644 --- a/lib/VM/JSLib/TypedArray.cpp +++ b/lib/VM/JSLib/TypedArray.cpp @@ -1746,6 +1746,126 @@ typedArrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } +inline static bool isValidIntegerIndex( + Runtime &runtime, + Handle O, + const HermesValue &index) { + // 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. + if (!O->attached(runtime)) { + return false; + } + + // 2. If IsIntegralNumber(index) is false, return false. + if (LLVM_UNLIKELY(!index.isNumber())) { + return false; + } + + // 3. If index is -0𝔽, return false. + if (LLVM_UNLIKELY( + index.getDouble() == 0 && std::signbit(index.getDouble()))) { + return false; + } + + // 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false. + if (index.getNumber() < 0 || index.getNumber() >= O->getLength()) { + return false; + } + + return true; +} + +/// ES14.0 23.2.3.36 +CallResult +typedArrayPrototypeWith(void *, Runtime &runtime, NativeArgs args) { + // 2. Perform ? ValidateTypedArray(O). + if (JSTypedArrayBase::validateTypedArray(runtime, args.getThisHandle()) == + ExecutionStatus::EXCEPTION) { + return ExecutionStatus::EXCEPTION; + } + + // 1. Let O be this value + auto self = args.vmcastThis(); + + // 3. Let len be O.[[ArrayLength]]. + double len = self->getLength(); + + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto relativeIndexRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); + if (LLVM_UNLIKELY(relativeIndexRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + // Use double here, because ToInteger may return Infinity. + double relativeIndex = relativeIndexRes->getNumber(); + + // 5. If relativeIndex ≥ 0, let actualIndex be relativeIndex. + // 6. Else, let actualIndex be len + relativeIndex. + double actualIndex = + convertNegativeBoundsRelativeToLength(relativeIndex, len); + + // 7. If O.[[ContentType]] is BigInt, let numericValueBe ? ToBigInt(value) + // 8. Else, let numericValue be ? ToNumber(value) + CallResult res = ExecutionStatus::EXCEPTION; + switch (self->getKind()) { + default: + res = toNumber_RJS(runtime, args.getArgHandle(1)); + break; + case CellKind::BigInt64ArrayKind: + case CellKind::BigUint64ArrayKind: + res = toBigInt_RJS(runtime, args.getArgHandle(1)); + break; + } + if (res == ExecutionStatus::EXCEPTION) { + return ExecutionStatus::EXCEPTION; + } + + HermesValue actualIndexValue = + HermesValue::encodeTrustedNumberValue(actualIndex); + // 9. If IsValidIntegerIndex(O, 𝔽(actualIndex)) is false, throw a RangeError + // exception. + if (LLVM_UNLIKELY(!isValidIntegerIndex(runtime, self, actualIndexValue))) { + return runtime.raiseRangeError("index invalid or out of range"); + } + + // 10. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »). + auto status = JSTypedArrayBase::allocateSpecies(runtime, self, len); + if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + auto A = status.getValue(); + if (actualIndex > 0) { + if (LLVM_UNLIKELY( + JSTypedArrayBase::setToCopyOfTypedArray( + runtime, A, 0, self, 0, actualIndex) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + } + + auto targetValue = runtime.makeHandle(res.getValue()); + if (LLVM_UNLIKELY( + A->setOwnIndexed(A, runtime, actualIndex, targetValue) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + if (actualIndex + 1 < len) { + if (LLVM_UNLIKELY( + JSTypedArrayBase::setToCopyOfTypedArray( + runtime, + A, + actualIndex + 1, + self, + actualIndex + 1, + len - actualIndex - 1) == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + } + + return A.getHermesValue(); +} + Handle createTypedArrayBaseConstructor(Runtime &runtime) { auto proto = Handle::vmcast(&runtime.typedArrayBasePrototype); @@ -2007,6 +2127,13 @@ Handle createTypedArrayBaseConstructor(Runtime &runtime) { (void *)IterationKind::Entry, typedArrayPrototypeIterator, 0); + defineMethod( + runtime, + proto, + Predefined::getSymbolID(Predefined::with), + nullptr, + typedArrayPrototypeWith, + 2); DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); diff --git a/test/hermes/TypedArray.js b/test/hermes/TypedArray.js index 4955e420cad..6154ace6f2f 100644 --- a/test/hermes/TypedArray.js +++ b/test/hermes/TypedArray.js @@ -1438,6 +1438,21 @@ cons.forEach(function(TypedArray) { }); /// @} +/// @name TypedArray.prototype.with +/// @{ +cons.forEach(function(TypedArray) { + var arr = new TypedArray([ 0, 1, 1, 3 ]); + + assert.arrayEqual(arr.with(2, 2), [ 0, 1, 2, 3 ]); + assert.arrayEqual(arr.with(-2, 2), [ 0, 1, 2, 3 ]); + assert.arrayEqual(new TypedArray(1).with(0, 100), [ 100 ]); + + assert.throws(function() { + arr.with(4, 5); + }, RangeError); +}); +/// @} + /// @name Exception cases /// @{