diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h index 15b8ce7fa482..dd171a6433e1 100644 --- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -11,6 +11,7 @@ #include "builtin/SelfHostingDefines.h" #include "vm/GlobalObject.h" +#include "vm/NativeObject.h" #include "vm/PIC.h" #include "vm/Runtime.h" @@ -270,8 +271,6 @@ class SetIteratorObject : public NativeObject extern bool InitSelfHostingCollectionIteratorFunctions(JSContext* cx, js::HandleObject obj); -bool IsPackedArray(JSObject* obj); - using SetInitGetPrototypeOp = NativeObject* (*)(JSContext*, Handle); using SetInitIsBuiltinOp = bool (*)(HandleValue, JSContext*); diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index ecb0994f7158..96067a2d04c5 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -1521,6 +1521,33 @@ function TypedArrayToStringTag() { } _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. function ArrayBufferSlice(start, end) { // Step 1. diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-generator.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-generator.js new file mode 100644 index 000000000000..233eeb9d92c2 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-generator.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-modified-array-iterator-next.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-modified-array-iterator-next.js new file mode 100644 index 000000000000..76eaaae46dfc --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-modified-array-iterator-next.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-modified-array-iterator.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-modified-array-iterator.js new file mode 100644 index 000000000000..7c3192a6a36d --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-modified-array-iterator.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-nonpacked-array.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-nonpacked-array.js new file mode 100644 index 000000000000..b9586a57e579 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-nonpacked-array.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-not-callable.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-not-callable.js new file mode 100644 index 000000000000..824760900a43 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-not-callable.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-packed-array-side-effect.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-packed-array-side-effect.js new file mode 100644 index 000000000000..3c00af0de219 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-packed-array-side-effect.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-packed-array.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-packed-array.js new file mode 100644 index 000000000000..495c2da496de --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-packed-array.js @@ -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); diff --git a/js/src/tests/ecma_6/TypedArray/constructor-iterable-undefined-or-null.js b/js/src/tests/ecma_6/TypedArray/constructor-iterable-undefined-or-null.js new file mode 100644 index 000000000000..48e5aaa6485a --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-iterable-undefined-or-null.js @@ -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); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 9c4e3f341b12..5831b5fc5231 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -165,6 +165,7 @@ macro(isFinite, isFinite, "isFinite") \ macro(isNaN, isNaN, "isNaN") \ macro(isPrototypeOf, isPrototypeOf, "isPrototypeOf") \ + macro(IterableToList, IterableToList, "IterableToList") \ macro(iterate, iterate, "iterate") \ macro(iteratorIntrinsic, iteratorIntrinsic, "__iterator__") \ macro(join, join, "join") \ diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 9672f0570174..f1f7d0e8ea5c 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1490,6 +1490,9 @@ MaybeNativeObject(JSObject* obj) return obj ? &obj->as() : nullptr; } +// Defined in NativeObject-inl.h. +bool IsPackedArray(JSObject* obj); + } // namespace js diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h index e1bf762fd1fe..d29c93a65372 100644 --- a/js/src/vm/TypedArrayCommon.h +++ b/js/src/vm/TypedArrayCommon.h @@ -22,6 +22,7 @@ #include "js/Conversions.h" #include "js/Value.h" +#include "vm/NativeObject.h" #include "vm/TypedArrayObject.h" namespace js { @@ -423,6 +424,64 @@ class ElementSpecific return true; } + /* + * Copy |source| into the typed array |target|. + */ + static bool + initFromIterablePackedArray(JSContext* cx, Handle 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 dest = + target->template as().viewDataEither().template cast(); + + 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 newDest = + target->template as().viewDataEither().template cast(); + Ops::store(newDest + i, n); + } + + return true; + } + private: static bool setFromOverlappingTypedArray(JSContext* cx, @@ -767,6 +826,60 @@ class TypedArrayMethods } MOZ_CRASH("bad target array type"); } + + static bool + initFromIterablePackedArray(JSContext* cx, Handle target, + HandleArrayObject source) + { + bool isShared = target->isSharedMemory(); + + switch (target->type()) { + case Scalar::Int8: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Uint8: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Int16: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Uint16: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Int32: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Uint32: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Float32: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Float64: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + case Scalar::Uint8Clamped: + if (isShared) + return ElementSpecific::initFromIterablePackedArray(cx, target, source); + return ElementSpecific::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 diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 70d942eccd41..2f71bc1b2eef 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -37,6 +37,7 @@ #include "vm/ArrayBufferObject.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" +#include "vm/PIC.h" #include "vm/SelfHosting.h" #include "vm/TypedArrayCommon.h" #include "vm/WrapperObject.h" @@ -1241,20 +1242,114 @@ TypedArrayObjectTemplate::fromTypedArray(JSContext* cx, HandleObject other, b return obj; } -// FIXME: This is not compatible with TypedArrayFrom in the spec -// (ES 2016 draft Mar 25, 2016 22.2.4.4 and 22.2.2.1.1) -// We should handle iterator protocol (bug 1232266). +static MOZ_ALWAYS_INLINE bool +IsOptimizableInit(JSContext* cx, HandleObject iterable, bool* optimized) +{ + MOZ_ASSERT(!*optimized); + + if (!IsPackedArray(iterable)) + return true; + + ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx); + if (!stubChain) + return false; + + return stubChain->tryOptimizeArray(cx, iterable.as(), optimized); +} + +// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e +// 22.2.4.4 TypedArray ( object ) template /* static */ JSObject* TypedArrayObjectTemplate::fromObject(JSContext* cx, HandleObject other, HandleObject newTarget) { + // Steps 1-2 (Already performed in caller). + + // Steps 3-4 (Allocation deferred until later). RootedObject proto(cx); - Rooted buffer(cx); - uint32_t len; - if (!GetLengthProperty(cx, other, &len)) - return nullptr; if (!GetPrototypeForInstance(cx, newTarget, &proto)) 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()); + + // Step 6.b. + uint32_t len = array->getDenseInitializedLength(); + + // Step 6.c. + Rooted buffer(cx); + if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) + return nullptr; + + Rooted obj(cx, makeInstance(cx, buffer, 0, len, proto)); + if (!obj) + return nullptr; + + // Steps 6.d-e. + if (!TypedArrayMethods::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 buffer(cx); if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; @@ -1262,8 +1357,11 @@ TypedArrayObjectTemplate::fromObject(JSContext* cx, HandleObject other, Handl if (!obj) return nullptr; - if (!TypedArrayMethods::setFromNonTypedArray(cx, obj, other, len)) + // Steps 11-12. + if (!TypedArrayMethods::setFromNonTypedArray(cx, obj, arrayLike, len)) return nullptr; + + // Step 13. return obj; }