Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions lib/VM/JSLib/TypedArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "hermes/Support/FastArraySearch.h"
#include "hermes/VM/ArrayStorage.h"
#include "hermes/VM/JSArrayBuffer.h"
#include "hermes/VM/JSLib.h"
#include "hermes/VM/JSLib/Base64Util.h"
#include "hermes/VM/JSLib/Sorting.h"
#include "hermes/VM/JSTypedArray.h"
Expand All @@ -30,6 +31,80 @@ namespace {
/// @name Abstract Operations and Helper functions
/// @{

/// Copies an array whose iterator and iterator-next methods are unmodified.
CallResult<HermesValue> iterableToArrayLikeFastPath(
Runtime &runtime,
Handle<JSArray> sourceArray) {
struct : public Locals {
PinnedValue<> nextValue;
PinnedValue<JSArray> A;
} lv;
LocalsRAII lraii{runtime, &lv};
GCScope gcScope{runtime};

uint32_t length = JSArray::getLength(*sourceArray, runtime);
if (arrayFastPathCheck(runtime, *sourceArray, nullptr, length)) {
auto arrRes = JSArray::create(runtime, length, 0);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
lv.A = std::move(*arrRes);
if (length == 0) {
return lv.A.getHermesValue();
}

auto newLength = SmallHermesValue::encodeNumberValue(length, runtime);
NoAllocScope noAllocScope{runtime};
auto *srcStorage = sourceArray->getIndexedStorageNullable(runtime);
auto *destStorage = lv.A->getIndexedStorageNullable(runtime);
assert(srcStorage && destStorage && "storage should not be null");
destStorage->appendWithinCapacity(runtime, srcStorage);
lv.A->setElemCountUnsafe(length);
JSArray::putLengthUnsafe(lv.A.get(), runtime, newLength);
for (uint32_t k = 0; k < length; ++k) {
if (LLVM_UNLIKELY(destStorage->at(k).isEmpty())) {
destStorage->set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test covering this path.

k, SmallHermesValue::encodeUndefinedValue(), runtime.getHeap());
}
}
return lv.A.getHermesValue();
}

auto arrRes = JSArray::create(runtime, 0, 0);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
lv.A = std::move(*arrRes);

uint32_t k = 0;
while (k < JSArray::getLength(*sourceArray, runtime)) {
GCScopeMarkerRAII marker{gcScope};

lv.nextValue = sourceArray->at(runtime, k).unboxToHV(runtime);
if (LLVM_UNLIKELY(lv.nextValue->isEmpty())) {
auto valueRes = getIndexed_RJS(runtime, sourceArray, k);
if (LLVM_UNLIKELY(valueRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
lv.nextValue = std::move(*valueRes);
}

if (LLVM_UNLIKELY(
JSArray::setElementAt(lv.A, runtime, k, lv.nextValue) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
++k;
}

if (LLVM_UNLIKELY(
JSArray::setLengthProperty(lv.A, runtime, k) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return lv.A.getHermesValue();
}

/// ES7 22.2.2.1.1 IterableToArrayLike
/// If items has an @@iterator method, iterate through it and collect
/// values into an array. Otherwise, return ToObject(items).
Expand Down Expand Up @@ -58,6 +133,34 @@ CallResult<HermesValue> iterableToArrayLike(Runtime &runtime, Handle<> items) {
return toObject(runtime, items);
}

// Avoid allocating iterator and iterator-result objects for ordinary arrays.
// Both methods must be unmodified because the generic iterator protocol
// observes each of them.
auto sourceArray = Handle<JSArray>::dyn_vmcast(items);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put this in a new method, iterableToArrayLikeFastPath?

if (sourceArray && lv.usingIterator->isObject() &&
lv.usingIterator->getObject() == *runtime.arrayPrototypeValues) {
auto iteratorPrototype =
Handle<JSObject>::vmcast(&runtime.arrayIteratorPrototype);
NamedPropertyDescriptor desc;
JSObject *propObj = JSObject::getNamedDescriptorPredefined(
iteratorPrototype, runtime, Predefined::next, desc);
if (LLVM_LIKELY(propObj) && LLVM_LIKELY(!desc.flags.accessor) &&
LLVM_LIKELY(!desc.flags.proxyObject) &&
LLVM_LIKELY(!desc.flags.hostObject)) {
SmallHermesValue nextMethod =
JSObject::getNamedSlotValueUnsafe(propObj, runtime, desc);
auto *nativeNext = nextMethod.isObject()
? dyn_vmcast<NativeFunction>(nextMethod.getObject(runtime))
: nullptr;
if (LLVM_LIKELY(nativeNext) &&
LLVM_LIKELY(
nativeNext->getFunctionPtr() == arrayIteratorPrototypeNext) &&
LLVM_LIKELY(nativeNext->getContext() == nullptr)) {
return iterableToArrayLikeFastPath(runtime, sourceArray);
}
}
}

// 3. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
auto iterRes = getCheckedIterator(
runtime, items, Handle<Callable>::vmcast(&lv.usingIterator));
Expand Down
127 changes: 127 additions & 0 deletions test/hermes/TypedArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,108 @@ cons.forEach(function(TypedArray) {
assert.equal(view[2], 3);
});

(function testArrayCopyWithDeletedElement() {
var source = [1, 2, 3];
delete source[1];

var view = new Float64Array(source);
assert.arrayEqual([view[0], view[1], view[2]], [1, NaN, 3]);

view = Uint8Array.from(source);
assert.arrayEqual([view[0], view[1], view[2]], [1, 0, 3]);

var visited = [];
view = Float64Array.from(source, function(value, index) {
visited.push(index);
if (index === 1) {
assert.equal(value, undefined);
return 7;
}
return value;
});
assert.arrayEqual([view[0], view[1], view[2]], [1, 7, 3]);
assert.arrayEqual(visited, [0, 1, 2]);
assert.equal(source.length, 3);
assert.equal(1 in source, false);
})();

(function testArrayIteratorFastPathSemantics() {
var dense = [1, 2, 3];
var view = new Float64Array(dense);
assert.arrayEqual([view[0], view[1], view[2]], dense);

var sparse = [1, , 3];
view = new Float64Array(sparse);
assert.equal(view[0], 1);
assert.equal(view[1], NaN);
assert.equal(view[2], 3);

Array.prototype[1] = 7;
try {
view = new Float64Array(sparse);
assert.equal(view[1], 7);
} finally {
delete Array.prototype[1];
}

var growing = [1, 2];
Object.defineProperty(growing, 0, {
get: function() {
growing.push(3);
return 1;
},
});
view = new Float64Array(growing);
assert.equal(view.length, 3);
assert.equal(view[2], 3);

var shrinking = [];
shrinking.length = 0xffffffff;
Object.defineProperty(shrinking, 0, {
get: function() {
shrinking.length = 1;
return 7;
},
});
view = new Float64Array(shrinking);
assert.equal(view.length, 1);
assert.equal(view[0], 7);

var source = [
{
valueOf: function() {
source[1] = 9;
return 1;
},
},
2,
];
view = new Float64Array(source);
assert.equal(view[0], 1);
assert.equal(view[1], 2);

source = [1, 2];
source[Symbol.iterator] = function*() {
yield 7;
yield 8;
};
view = new Float64Array(source);
assert.equal(view[0], 7);
assert.equal(view[1], 8);

var iteratorPrototype = Object.getPrototypeOf([][Symbol.iterator]());
var originalNext = iteratorPrototype.next;
iteratorPrototype.next = function() {
return {value: 11, done: true};
};
try {
view = new Float64Array([1, 2]);
assert.equal(view.length, 0);
} finally {
iteratorPrototype.next = originalNext;
}
})();

// Check constructor from Array with arbitrary types
cons.forEach(function(TypedArray) {
var view = new TypedArray(['1', {}, false]);
Expand Down Expand Up @@ -633,6 +735,31 @@ cons.forEach(function(c, i) {
assert.equal(ta[2], 6);
});

(function testFromArrayIteratorFastPathSemantics() {
var source = [
{
valueOf: function() {
source[1] = 9;
return 1;
},
},
2,
];
var ta = Float64Array.from(source);
assert.equal(ta[0], 1);
assert.equal(ta[1], 2);

source = [1, 2];
ta = Float64Array.from(source, function(value, index) {
if (index === 0) {
source[1] = 9;
}
return value;
});
assert.equal(ta[0], 1);
assert.equal(ta[1], 2);
})();

/// @}

/// @name %TypedArray%.of
Expand Down