Skip to content

Commit

Permalink
Implement Array.prototype.find and Array.prototype.findIndex in JS
Browse files Browse the repository at this point in the history
Summary: Implement `Array.prototype.find` and `Array.prototype.findIndex` in JS

Reviewed By: dulinriley

Differential Revision: D17637660

fbshipit-source-id: 0e5ae8e5dd847e19acd6d8f76b83ed55637f6e9e
  • Loading branch information
Kiwi137 authored and facebook-github-bot committed Oct 11, 2019
1 parent cf68f40 commit 4663431
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 82 deletions.
2 changes: 1 addition & 1 deletion include/hermes/VM/NativeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ NATIVE_FUNCTION(arrayIteratorPrototypeNext)
NATIVE_FUNCTION(arrayOf)
NATIVE_FUNCTION(arrayPrototypeConcat)
NATIVE_FUNCTION(arrayPrototypeCopyWithin)
NATIVE_FUNCTION(arrayPrototypeFind)
NATIVE_FUNCTION(arrayPrototypeForEach)
NATIVE_FUNCTION(arrayPrototypeIncludes)
NATIVE_FUNCTION(arrayPrototypeIterator)
Expand All @@ -61,6 +60,7 @@ NATIVE_FUNCTION(arrayPrototypeUnshift)
NATIVE_FUNCTION(arrayPrototypeEvery)
NATIVE_FUNCTION(arrayPrototypeFill)
NATIVE_FUNCTION(arrayPrototypeFilter)
NATIVE_FUNCTION(arrayPrototypeFind)
NATIVE_FUNCTION(arrayPrototypeIndexOf)
NATIVE_FUNCTION(arrayPrototypeLastIndexOf)
NATIVE_FUNCTION(arrayPrototypeMap)
Expand Down
100 changes: 100 additions & 0 deletions lib/InternalBytecode/Array.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,102 @@ Array.prototype.filter = function filter(callbackfn, thisArg = undefined) {
return A;
}

/**
* Helper function for Array.prototype.find and Array.prototypr.findIndex,
* see their doc-comments for more details.
* @param arr an array-like value.
* @param predicate function to be invoked on the elements.
* @param thisArg parameter which will be used as the <this> value
* for each invocation of predicate.
* @param returnElement boolean flag for whether to return the element found
* or index of the element
* @return if returnElement is true, returns the first element which makes
* the predicate return a truthy value, and undefined if no such
* element exists.
* If returnElement is false, return index of the first element which
* makes the predicate return a truthy value, and -1 if no such
* element exists.
* @throw TypeError if predicate is not a function
*/
function arrayPrototypeFindHelper(arr, predicate, thisArg, returnElement) {
// Array.prototype.find and Array.prototype.findIndex cannot be used as constructors
if (new.target) throw _TypeError('This function cannot be used as a constructor.');

// 1. Let O be ? ToObject(this value).
var O = HermesInternal.toObject(arr);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = HermesInternal.toLength(O.length);

// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (typeof predicate !== 'function') {
throw _TypeError('Find argument must be a function');
}

// 4. If thisArg is present, let T be thisArg; else let T be undefined.

// 5. Let k be 0.
var k = 0;

// fast path which calls the function directly when thisArg is undefined
if (thisArg === undefined) {
while (k < len) {
var kValue = O[k];
if (predicate(kValue, k, O)) return returnElement ? kValue : k;
k += 1;
}
return returnElement ? undefined : -1;
}

// 6. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(O, Pk).
var kValue = O[k];
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
// d. If testResult is true, return kValue. (Array.prototype.find)
// d. If testResult is true, return k. (Array.prototype.findIndex)
if (HermesInternal.executeCall(predicate, thisArg, kValue, k, O)) {
return returnElement ? kValue : k;
}
// e. Increase k by 1.
k += 1;
}

// 7. Return undefined. (Array.prototype.find)
// 7. Return -1. (Array.prototype.findIndex)
return returnElement ? undefined : -1;
}

/**
* ES10.0 22.1.3.8
* Invoke a predicate function on elements in an array-like object until
* a return value is truthy or there's no more elments.
* @this an array-like value.
* @param predicate function to be invoked on the elements.
* @param [thisArg] optional parameter which will be used as the <this> value
* for each invocation of predicate.
* @return the first element which makes the predicate return a truthy value,
* and undefined if no such element exists
*/
Array.prototype.find = function find(predicate, thisArg = undefined) {
return arrayPrototypeFindHelper(this, predicate, thisArg, true);
}

/**
* ES10.0 22.1.3.9
* Invoke a predicate function on elements in an array-like object until
* a return value is truthy or there's no more elments.
* @this an array-like value.
* @param predicate function to be invoked on the elements.
* @param [thisArg] optional parameter which will be used as the <this> value
* for each invocation of predicate.
* @return index of the first element which makes the predicate return a
* truthy value, and -1 if no such element exists
*/
Array.prototype.findIndex = function findIndex(predicate, thisArg = undefined) {
return arrayPrototypeFindHelper(this, predicate, thisArg, false);
}

var arrayFuncDescriptor = {
configurable: true,
enumerable: false,
Expand All @@ -485,6 +581,8 @@ Object.defineProperty(Array.prototype, 'every', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'some', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'fill', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'filter', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'find', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'findIndex', arrayFuncDescriptor);

Array.prototype.reverse.prototype = undefined;
Array.prototype.map.prototype = undefined;
Expand All @@ -494,3 +592,5 @@ Array.prototype.every.prototype = undefined;
Array.prototype.some.prototype = undefined;
Array.prototype.fill.prototype = undefined;
Array.prototype.filter.prototype = undefined;
Array.prototype.find.prototype = undefined;
Array.prototype.findIndex.prototype = undefined;
162 changes: 81 additions & 81 deletions lib/VM/JSLib/Array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,6 @@ Handle<JSObject> createArrayConstructor(Runtime *runtime) {
nullptr,
arrayPrototypeForEach,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::find),
nullptr,
arrayPrototypeFind,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::findIndex),
// Pass a non-null pointer here to indicate we're finding the index.
(void *)true,
arrayPrototypeFind,
1);
defineMethod(
runtime,
arrayPrototype,
Expand Down Expand Up @@ -225,6 +210,21 @@ Handle<JSObject> createArrayConstructor(Runtime *runtime) {
nullptr,
arrayPrototypeFill,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::find),
nullptr,
arrayPrototypeFind,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::findIndex),
// Pass a non-null pointer here to indicate we're finding the index.
(void *)true,
arrayPrototypeFind,
1);
defineMethod(
runtime,
arrayPrototype,
Expand Down Expand Up @@ -2121,72 +2121,6 @@ arrayPrototypeForEach(void *, Runtime *runtime, NativeArgs args) {
return HermesValue::encodeUndefinedValue();
}

CallResult<HermesValue>
arrayPrototypeFind(void *ctx, Runtime *runtime, NativeArgs args) {
GCScope gcScope{runtime};
bool findIndex = ctx != nullptr;
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime->makeHandle<JSObject>(objRes.getValue());

// Get the length.
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime->makeHandle(*propRes));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *intRes;

auto predicate = args.dyncastArg<Callable>(0);
if (!predicate) {
return runtime->raiseTypeError("Find argument must be a function");
}

// "this" argument to the callback function.
auto T = args.getArgHandle(1);

MutableHandle<> kHandle{runtime, HermesValue::encodeNumberValue(0)};
MutableHandle<> kValue{runtime};
auto marker = gcScope.createMarker();
while (kHandle->getNumber() < len) {
gcScope.flushToMarker(marker);
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(O, runtime, kHandle)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
kValue = *propRes;
auto callRes = Callable::executeCall3(
predicate,
runtime,
T,
kValue.getHermesValue(),
kHandle.getHermesValue(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
bool testResult = toBoolean(*callRes);
if (testResult) {
// If this is Array.prototype.findIndex, then return the index k.
// Else, return the value at the index k.
return findIndex ? kHandle.getHermesValue() : kValue.getHermesValue();
}
kHandle = HermesValue::encodeNumberValue(kHandle->getNumber() + 1);
}

// Failure case for Array.prototype.findIndex is -1.
// Failure case for Array.prototype.find is undefined.
return findIndex ? HermesValue::encodeNumberValue(-1)
: HermesValue::encodeUndefinedValue();
}

/// Helper for reduce and reduceRight.
/// \param reverse set to true to reduceRight, false to reduce from the left.
static inline CallResult<HermesValue>
Expand Down Expand Up @@ -3033,6 +2967,72 @@ arrayPrototypeFilter(void *, Runtime *runtime, NativeArgs args) {
return A.getHermesValue();
}

CallResult<HermesValue>
arrayPrototypeFind(void *ctx, Runtime *runtime, NativeArgs args) {
GCScope gcScope{runtime};
bool findIndex = ctx != nullptr;
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime->makeHandle<JSObject>(objRes.getValue());

// Get the length.
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime->makeHandle(*propRes));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *intRes;

auto predicate = args.dyncastArg<Callable>(0);
if (!predicate) {
return runtime->raiseTypeError("Find argument must be a function");
}

// "this" argument to the callback function.
auto T = args.getArgHandle(1);

MutableHandle<> kHandle{runtime, HermesValue::encodeNumberValue(0)};
MutableHandle<> kValue{runtime};
auto marker = gcScope.createMarker();
while (kHandle->getNumber() < len) {
gcScope.flushToMarker(marker);
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(O, runtime, kHandle)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
kValue = *propRes;
auto callRes = Callable::executeCall3(
predicate,
runtime,
T,
kValue.getHermesValue(),
kHandle.getHermesValue(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
bool testResult = toBoolean(*callRes);
if (testResult) {
// If this is Array.prototype.findIndex, then return the index k.
// Else, return the value at the index k.
return findIndex ? kHandle.getHermesValue() : kValue.getHermesValue();
}
kHandle = HermesValue::encodeNumberValue(kHandle->getNumber() + 1);
}

// Failure case for Array.prototype.findIndex is -1.
// Failure case for Array.prototype.find is undefined.
return findIndex ? HermesValue::encodeNumberValue(-1)
: HermesValue::encodeUndefinedValue();
}

CallResult<HermesValue>
arrayPrototypeFill(void *, Runtime *runtime, NativeArgs args) {
GCScope gcScope(runtime);
Expand Down
31 changes: 31 additions & 0 deletions tools/hvm-bench/arrayFind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var USE_THISARG = false;

var numIter = 4000;
var len = 10000;
var thisArg;
var a = Array(len);
for (var i = 0; i < len; i++) {
a[i] = i;
}

if (USE_THISARG) thisArg = a;

function nonFail(val) {
return val > 0;
}

function allFail(val) {
return val < 0;
}

function halfFail(val) {
return val >= 5000;
}

for (var i = 0; i < numIter; i++) {
a.find(nonFail, thisArg);
a.find(allFail, thisArg);
a.find(halfFail, thisArg);
}

print('done');
31 changes: 31 additions & 0 deletions tools/hvm-bench/arrayFindIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var USE_THISARG = false;

var numIter = 4000;
var len = 10000;
var thisArg;
var a = Array(len);
for (var i = 0; i < len; i++) {
a[i] = i;
}

if (USE_THISARG) thisArg = a;

function nonFail(val) {
return val > 0;
}

function allFail(val) {
return val < 0;
}

function halfFail(val) {
return val >= 5000;
}

for (var i = 0; i < numIter; i++) {
a.findIndex(nonFail, thisArg);
a.findIndex(allFail, thisArg);
a.findIndex(halfFail, thisArg);
}

print('done');

0 comments on commit 4663431

Please sign in to comment.