Bug 1232266: Support iterables in TypedArray constructors. r=jandem

This commit is contained in:
André Bargull 2016-10-24 08:53:43 -07:00
Родитель a9fedb7608
Коммит 3386f0c48a
14 изменённых файлов: 410 добавлений и 10 удалений

Просмотреть файл

@ -11,6 +11,7 @@
#include "builtin/SelfHostingDefines.h" #include "builtin/SelfHostingDefines.h"
#include "vm/GlobalObject.h" #include "vm/GlobalObject.h"
#include "vm/NativeObject.h"
#include "vm/PIC.h" #include "vm/PIC.h"
#include "vm/Runtime.h" #include "vm/Runtime.h"
@ -270,8 +271,6 @@ class SetIteratorObject : public NativeObject
extern bool extern bool
InitSelfHostingCollectionIteratorFunctions(JSContext* cx, js::HandleObject obj); InitSelfHostingCollectionIteratorFunctions(JSContext* cx, js::HandleObject obj);
bool IsPackedArray(JSObject* obj);
using SetInitGetPrototypeOp = NativeObject* (*)(JSContext*, Handle<GlobalObject*>); using SetInitGetPrototypeOp = NativeObject* (*)(JSContext*, Handle<GlobalObject*>);
using SetInitIsBuiltinOp = bool (*)(HandleValue, JSContext*); using SetInitIsBuiltinOp = bool (*)(HandleValue, JSContext*);

Просмотреть файл

@ -1521,6 +1521,33 @@ function TypedArrayToStringTag() {
} }
_SetCanonicalName(TypedArrayToStringTag, "get [Symbol.toStringTag]"); _SetCanonicalName(TypedArrayToStringTag, "get [Symbol.toStringTag]");
// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
// 22.2.2.1.1 Runtime Semantics: IterableToList( items, method )
function IterableToList(items, method) {
// Step 1.
var iterator = GetIterator(items, method);
// Step 2.
var values = [];
// Steps 3-4.
var i = 0;
while (true) {
// Step 4.a.
var next = callContentFunction(iterator.next, iterator);
if (!IsObject(next))
ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
// Step 4.b.
if (next.done)
break;
_DefineDataProperty(values, i++, next.value);
}
// Step 5.
return values;
}
// ES 2016 draft Mar 25, 2016 24.1.4.3. // ES 2016 draft Mar 25, 2016 24.1.4.3.
function ArrayBufferSlice(start, end) { function ArrayBufferSlice(start, end) {
// Step 1. // Step 1.

Просмотреть файл

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from generator.
for (let constructor of anyTypedArrayConstructors) {
for (let array of [[], [1], [2, 3], [4, 5, 6], Array(1024).fill(0).map((v, i) => i % 128)]) {
let typedArray = new constructor(function*(){ yield* array; }());
assertEq(typedArray.length, array.length);
assertEqArray(typedArray, array);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from array with modified array iterator next method.
const ArrayIteratorPrototype = Object.getPrototypeOf([][Symbol.iterator]());
const origNext = ArrayIteratorPrototype.next;
const modifiedNext = function() {
let {value, done} = origNext.call(this);
return {value: value * 5, done};
};
for (let constructor of anyTypedArrayConstructors) {
for (let array of [[], [1], [2, 3], [4, 5, 6], Array(1024).fill(0).map((v, i) => i % 24)]) {
ArrayIteratorPrototype.next = modifiedNext;
let typedArray;
try {
typedArray = new constructor(array);
} finally {
ArrayIteratorPrototype.next = origNext;
}
assertEq(typedArray.length, array.length);
assertEqArray(typedArray, array.map(v => v * 5));
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from array with modified array iterator.
const origIterator = Array.prototype[Symbol.iterator];
const modifiedIterator = function*() {
for (let v of origIterator.call(this)) {
yield v * 5;
}
};
for (let constructor of anyTypedArrayConstructors) {
for (let array of [[], [1], [2, 3], [4, 5, 6], Array(1024).fill(0).map((v, i) => i % 24)]) {
Array.prototype[Symbol.iterator] = modifiedIterator;
let typedArray;
try {
typedArray = new constructor(array)
} finally {
Array.prototype[Symbol.iterator] = origIterator;
}
assertEq(typedArray.length, array.length);
assertEqArray(typedArray, array.map(v => v * 5));
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from iterable non-packed array.
for (let constructor of anyTypedArrayConstructors) {
for (let array of [[,], [,,], Array(1024)]) {
let typedArray = new constructor(array);
assertEq(typedArray.length, array.length);
let expectedArray = Array(array.length).fill(isFloatConstructor(constructor) ? NaN : 0);
assertEqArray(typedArray, expectedArray);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from object with non-callable [Symbol.iterator] property.
for (let constructor of anyTypedArrayConstructors) {
for (let iterator of [true, 0, Math.PI, "", "10", Symbol.iterator, {}, []]) {
assertThrowsInstanceOf(() => new constructor({
[Symbol.iterator]: iterator
}), TypeError);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from iterable packed array, array contains object with modifying
// valueOf() method.
for (let constructor of anyTypedArrayConstructors) {
let array = [
0, 1, {valueOf() { array[3] = 30; return 2; }}, 3, 4
];
let typedArray = new constructor(array);
assertEq(typedArray.length, array.length);
assertEqArray(typedArray, [0, 1, 2, 3, 4]);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from iterable packed array.
for (let constructor of anyTypedArrayConstructors) {
for (let array of [[], [1], [2, 3], [4, 5, 6], Array(1024).fill(0).map((v, i) => i % 128)]) {
let typedArray = new constructor(array);
assertEq(typedArray.length, array.length);
assertEqArray(typedArray, array);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Construct typed array from object with undefined or null [Symbol.iterator] property.
for (let constructor of anyTypedArrayConstructors) {
for (let iterator of [undefined, null]) {
let arrayLike = {
[Symbol.iterator]: iterator,
length: 2,
0: 10,
1: 20,
};
let typedArray = new constructor(arrayLike);
assertEq(typedArray.length, arrayLike.length);
assertEqArray(typedArray, arrayLike);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

Просмотреть файл

@ -165,6 +165,7 @@
macro(isFinite, isFinite, "isFinite") \ macro(isFinite, isFinite, "isFinite") \
macro(isNaN, isNaN, "isNaN") \ macro(isNaN, isNaN, "isNaN") \
macro(isPrototypeOf, isPrototypeOf, "isPrototypeOf") \ macro(isPrototypeOf, isPrototypeOf, "isPrototypeOf") \
macro(IterableToList, IterableToList, "IterableToList") \
macro(iterate, iterate, "iterate") \ macro(iterate, iterate, "iterate") \
macro(iteratorIntrinsic, iteratorIntrinsic, "__iterator__") \ macro(iteratorIntrinsic, iteratorIntrinsic, "__iterator__") \
macro(join, join, "join") \ macro(join, join, "join") \

Просмотреть файл

@ -1490,6 +1490,9 @@ MaybeNativeObject(JSObject* obj)
return obj ? &obj->as<NativeObject>() : nullptr; return obj ? &obj->as<NativeObject>() : nullptr;
} }
// Defined in NativeObject-inl.h.
bool IsPackedArray(JSObject* obj);
} // namespace js } // namespace js

Просмотреть файл

@ -22,6 +22,7 @@
#include "js/Conversions.h" #include "js/Conversions.h"
#include "js/Value.h" #include "js/Value.h"
#include "vm/NativeObject.h"
#include "vm/TypedArrayObject.h" #include "vm/TypedArrayObject.h"
namespace js { namespace js {
@ -423,6 +424,64 @@ class ElementSpecific
return true; return true;
} }
/*
* Copy |source| into the typed array |target|.
*/
static bool
initFromIterablePackedArray(JSContext* cx, Handle<SomeTypedArray*> target,
HandleArrayObject source)
{
MOZ_ASSERT(target->type() == SpecificArray::ArrayTypeID(),
"target type and NativeType must match");
MOZ_ASSERT(IsPackedArray(source), "source array must be packed");
MOZ_ASSERT(source->getDenseInitializedLength() <= target->length());
uint32_t len = source->getDenseInitializedLength();
uint32_t i = 0;
// Attempt fast-path infallible conversion of dense elements up to the
// first potentially side-effectful conversion.
SharedMem<T*> dest =
target->template as<TypedArrayObject>().viewDataEither().template cast<T*>();
const Value* srcValues = source->getDenseElements();
for (; i < len; i++) {
if (!canConvertInfallibly(srcValues[i]))
break;
Ops::store(dest + i, infallibleValueToNative(srcValues[i]));
}
if (i == len)
return true;
// Convert any remaining elements by first collecting them into a
// temporary list, and then copying them into the typed array.
AutoValueVector values(cx);
if (!values.append(srcValues + i, len - i))
return false;
RootedValue v(cx);
for (uint32_t j = 0; j < values.length(); i++, j++) {
v = values[j];
T n;
if (!valueToNative(cx, v, &n))
return false;
// |target| is a newly allocated typed array and not yet visible to
// content script, so valueToNative can't detach the underlying
// buffer.
MOZ_ASSERT(i < target->length());
// Compute every iteration in case GC moves the data.
SharedMem<T*> newDest =
target->template as<TypedArrayObject>().viewDataEither().template cast<T*>();
Ops::store(newDest + i, n);
}
return true;
}
private: private:
static bool static bool
setFromOverlappingTypedArray(JSContext* cx, setFromOverlappingTypedArray(JSContext* cx,
@ -767,6 +826,60 @@ class TypedArrayMethods
} }
MOZ_CRASH("bad target array type"); MOZ_CRASH("bad target array type");
} }
static bool
initFromIterablePackedArray(JSContext* cx, Handle<SomeTypedArray*> target,
HandleArrayObject source)
{
bool isShared = target->isSharedMemory();
switch (target->type()) {
case Scalar::Int8:
if (isShared)
return ElementSpecific<Int8ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Int8ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Uint8:
if (isShared)
return ElementSpecific<Uint8ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Uint8ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Int16:
if (isShared)
return ElementSpecific<Int16ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Int16ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Uint16:
if (isShared)
return ElementSpecific<Uint16ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Uint16ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Int32:
if (isShared)
return ElementSpecific<Int32ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Int32ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Uint32:
if (isShared)
return ElementSpecific<Uint32ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Uint32ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Float32:
if (isShared)
return ElementSpecific<Float32ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Float32ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Float64:
if (isShared)
return ElementSpecific<Float64ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Float64ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Uint8Clamped:
if (isShared)
return ElementSpecific<Uint8ClampedArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source);
return ElementSpecific<Uint8ClampedArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source);
case Scalar::Int64:
case Scalar::Float32x4:
case Scalar::Int8x16:
case Scalar::Int16x8:
case Scalar::Int32x4:
case Scalar::MaxTypedArrayViewType:
break;
}
MOZ_CRASH("bad target array type");
}
}; };
} // namespace js } // namespace js

Просмотреть файл

@ -37,6 +37,7 @@
#include "vm/ArrayBufferObject.h" #include "vm/ArrayBufferObject.h"
#include "vm/GlobalObject.h" #include "vm/GlobalObject.h"
#include "vm/Interpreter.h" #include "vm/Interpreter.h"
#include "vm/PIC.h"
#include "vm/SelfHosting.h" #include "vm/SelfHosting.h"
#include "vm/TypedArrayCommon.h" #include "vm/TypedArrayCommon.h"
#include "vm/WrapperObject.h" #include "vm/WrapperObject.h"
@ -1241,20 +1242,114 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, b
return obj; return obj;
} }
// FIXME: This is not compatible with TypedArrayFrom in the spec static MOZ_ALWAYS_INLINE bool
// (ES 2016 draft Mar 25, 2016 22.2.4.4 and 22.2.2.1.1) IsOptimizableInit(JSContext* cx, HandleObject iterable, bool* optimized)
// We should handle iterator protocol (bug 1232266). {
MOZ_ASSERT(!*optimized);
if (!IsPackedArray(iterable))
return true;
ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
if (!stubChain)
return false;
return stubChain->tryOptimizeArray(cx, iterable.as<ArrayObject>(), optimized);
}
// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
// 22.2.4.4 TypedArray ( object )
template<typename T> template<typename T>
/* static */ JSObject* /* static */ JSObject*
TypedArrayObjectTemplate<T>::fromObject(JSContext* cx, HandleObject other, HandleObject newTarget) TypedArrayObjectTemplate<T>::fromObject(JSContext* cx, HandleObject other, HandleObject newTarget)
{ {
// Steps 1-2 (Already performed in caller).
// Steps 3-4 (Allocation deferred until later).
RootedObject proto(cx); RootedObject proto(cx);
Rooted<ArrayBufferObject*> buffer(cx);
uint32_t len;
if (!GetLengthProperty(cx, other, &len))
return nullptr;
if (!GetPrototypeForInstance(cx, newTarget, &proto)) if (!GetPrototypeForInstance(cx, newTarget, &proto))
return nullptr; return nullptr;
bool optimized = false;
if (!IsOptimizableInit(cx, other, &optimized))
return nullptr;
// Fast path when iterable is a packed array using the default iterator.
if (optimized) {
// Step 6.a (We don't need to call IterableToList for the fast path).
RootedArrayObject array(cx, &other->as<ArrayObject>());
// Step 6.b.
uint32_t len = array->getDenseInitializedLength();
// Step 6.c.
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer))
return nullptr;
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
if (!obj)
return nullptr;
// Steps 6.d-e.
if (!TypedArrayMethods<TypedArrayObject>::initFromIterablePackedArray(cx, obj, array))
return nullptr;
// Step 6.f (The assertion isn't applicable for the fast path).
// Step 6.g.
return obj;
}
// Step 5.
RootedValue callee(cx);
RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
if (!GetProperty(cx, other, other, iteratorId, &callee))
return nullptr;
// Steps 6-8.
RootedObject arrayLike(cx);
if (!callee.isNullOrUndefined()) {
// Throw if other[Symbol.iterator] isn't callable.
if (!callee.isObject() || !callee.toObject().isCallable()) {
RootedValue otherVal(cx, ObjectValue(*other));
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, otherVal, nullptr);
if (!bytes)
return nullptr;
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
bytes.get());
return nullptr;
}
FixedInvokeArgs<2> args2(cx);
args2[0].setObject(*other);
args2[1].set(callee);
// Step 6.a.
RootedValue rval(cx);
if (!CallSelfHostedFunction(cx, cx->names().IterableToList, UndefinedHandleValue, args2,
&rval))
{
return nullptr;
}
// Steps 6.b-g (Implemented in steps 9-13 below).
arrayLike = &rval.toObject();
} else {
// Step 7 is an assertion: object is not an Iterator. Testing this is
// literally the very last thing we did, so we don't assert here.
// Step 8.
arrayLike = other;
}
// Step 9.
uint32_t len;
if (!GetLengthProperty(cx, arrayLike, &len))
return nullptr;
// Step 10.
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer))
return nullptr; return nullptr;
@ -1262,8 +1357,11 @@ TypedArrayObjectTemplate<T>::fromObject(JSContext* cx, HandleObject other, Handl
if (!obj) if (!obj)
return nullptr; return nullptr;
if (!TypedArrayMethods<TypedArrayObject>::setFromNonTypedArray(cx, obj, other, len)) // Steps 11-12.
if (!TypedArrayMethods<TypedArrayObject>::setFromNonTypedArray(cx, obj, arrayLike, len))
return nullptr; return nullptr;
// Step 13.
return obj; return obj;
} }