From cc3f59db556efe4e31813326510730cccafe801b Mon Sep 17 00:00:00 2001 From: dehrenberg Date: Mon, 18 May 2015 13:13:56 -0700 Subject: [PATCH] Add TypedArray.from method This function creates a new way to make TypedArrays based on existing iterable or Array-like objects, analogous to Array.from. The patch implements the function and adds tests. R=arv@chromium.org BUG=v8:3578 LOG=Y Review URL: https://codereview.chromium.org/1132163011 Cr-Commit-Position: refs/heads/master@{#28456} --- src/harmony-array.js | 2 + src/harmony-typedarray.js | 29 +++++- test/mjsunit/harmony/typedarray-from.js | 123 ++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 test/mjsunit/harmony/typedarray-from.js diff --git a/src/harmony-array.js b/src/harmony-array.js index c0de2b9022..93f32a82b2 100644 --- a/src/harmony-array.js +++ b/src/harmony-array.js @@ -6,6 +6,7 @@ var $innerArrayCopyWithin; var $innerArrayFill; var $innerArrayFind; var $innerArrayFindIndex; +var $arrayFrom; (function(global, exports) { @@ -254,6 +255,7 @@ function ArrayFrom(arrayLike, mapfn, receiver) { return result; } } +$arrayFrom = ArrayFrom; // ES6, draft 05-22-14, section 22.1.2.3 function ArrayOf() { diff --git a/src/harmony-typedarray.js b/src/harmony-typedarray.js index 3c4c4a40fe..90679e0c1f 100644 --- a/src/harmony-typedarray.js +++ b/src/harmony-typedarray.js @@ -26,6 +26,7 @@ var GlobalNAME = global.NAME; endmacro TYPED_ARRAYS(DECLARE_GLOBALS) +DECLARE_GLOBALS(Array) // ------------------------------------------------------------------- @@ -34,7 +35,7 @@ function TypedArrayCopyWithin(target, start, end) { var length = %_TypedArrayGetLength(this); - // TODO(dehrenberg): Replace with a memcpy for better performance + // TODO(littledan): Replace with a memcpy for better performance return $innerArrayCopyWithin(target, start, end, this, length); } %FunctionSetLength(TypedArrayCopyWithin, 2); @@ -100,9 +101,35 @@ function TypedArrayOf() { return array; } +function ConstructTypedArray(constructor, array) { + // TODO(littledan): This is an approximation of the spec, which requires + // that only real TypedArray classes should be accepted (22.2.2.1.1) + if (!IS_SPEC_OBJECT(constructor) || IS_UNDEFINED(constructor.prototype) || + !%HasOwnProperty(constructor.prototype, "BYTES_PER_ELEMENT")) { + throw MakeTypeError(kNotTypedArray); + } + + // TODO(littledan): The spec requires that, rather than directly calling + // the constructor, a TypedArray is created with the proper proto and + // underlying size and element size, and elements are put in one by one. + // By contrast, this would allow subclasses to make a radically different + // constructor with different semantics. + return new constructor(array); +} + +function TypedArrayFrom(source, mapfn, thisArg) { + // TODO(littledan): Investigate if there is a receiver which could be + // faster to accumulate on than Array, e.g., a TypedVector. + var array = %_CallFunction(GlobalArray, source, mapfn, thisArg, $arrayFrom); + return ConstructTypedArray(this, array); +} +%FunctionSetLength(TypedArrayFrom, 1); + +// TODO(littledan): Fix the TypedArray proto chain (bug v8:4085). macro EXTEND_TYPED_ARRAY(NAME) // Set up non-enumerable functions on the object. $installFunctions(GlobalNAME, DONT_ENUM | DONT_DELETE | READ_ONLY, [ + "from", TypedArrayFrom, "of", TypedArrayOf ]); diff --git a/test/mjsunit/harmony/typedarray-from.js b/test/mjsunit/harmony/typedarray-from.js new file mode 100644 index 0000000000..1529384acc --- /dev/null +++ b/test/mjsunit/harmony/typedarray-from.js @@ -0,0 +1,123 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-arrays + +var typedArrayConstructors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Uint8ClampedArray, + Float32Array, + Float64Array +]; + +for (var constructor of typedArrayConstructors) { + assertEquals(1, constructor.from.length); + + // TypedArray.from only callable on this subclassing %TypedArray% + assertThrows(function () {constructor.from.call(Array, [])}, TypeError); + + function assertArrayLikeEquals(value, expected, type) { + assertEquals(value.__proto__, type.prototype); + assertEquals(expected.length, value.length); + for (var i = 0; i < value.length; ++i) { + assertEquals(expected[i], value[i]); + } + } + + // Assert that calling mapfn with / without thisArg in sloppy and strict modes + // works as expected. + var global = this; + function non_strict() { assertEquals(global, this); } + function strict() { 'use strict'; assertEquals(undefined, this); } + function strict_null() { 'use strict'; assertEquals(null, this); } + constructor.from([1], non_strict); + constructor.from([1], non_strict, void 0); + constructor.from([1], non_strict, null); + constructor.from([1], strict); + constructor.from([1], strict, void 0); + constructor.from([1], strict_null, null); + + // TypedArray.from can only be called on TypedArray constructors + assertThrows(function() {constructor.from.call({}, [])}, TypeError); + assertThrows(function() {constructor.from.call([], [])}, TypeError); + assertThrows(function() {constructor.from.call(1, [])}, TypeError); + assertThrows(function() {constructor.from.call(undefined, [])}, TypeError); + + // Converting from various other types, demonstrating that it can + // operate on array-like objects as well as iterables. + // TODO(littledan): constructors should have similar flexibility. + assertArrayLikeEquals(constructor.from( + { length: 1, 0: 5 }), [5], constructor); + + assertArrayLikeEquals(constructor.from( + { length: -1, 0: 5 }), [], constructor); + + assertArrayLikeEquals(constructor.from( + [1, 2, 3]), [1, 2, 3], constructor); + + var set = new Set([1, 2, 3]); + assertArrayLikeEquals(constructor.from(set), [1, 2, 3], + constructor); + + function* generator() { + yield 4; + yield 5; + yield 6; + } + + assertArrayLikeEquals(constructor.from(generator()), + [4, 5, 6], constructor); + + assertThrows(function() { constructor.from(null); }, TypeError); + assertThrows(function() { constructor.from(undefined); }, TypeError); + assertThrows(function() { constructor.from([], null); }, TypeError); + assertThrows(function() { constructor.from([], 'noncallable'); }, + TypeError); + + var nullIterator = {}; + nullIterator[Symbol.iterator] = null; + assertArrayLikeEquals(constructor.from(nullIterator), [], + constructor); + + var nonObjIterator = {}; + nonObjIterator[Symbol.iterator] = function() { return 'nonObject'; }; + assertThrows(function() { constructor.from(nonObjIterator); }, + TypeError); + + assertThrows(function() { constructor.from([], null); }, TypeError); + + // Ensure iterator is only accessed once, and only invoked once + var called = 0; + var arr = [1, 2, 3]; + var obj = {}; + var counter = 0; + + // Test order --- only get iterator method once + function testIterator() { + called++; + assertEquals(obj, this); + return arr[Symbol.iterator](); + } + var getCalled = 0; + Object.defineProperty(obj, Symbol.iterator, { + get: function() { + getCalled++; + return testIterator; + }, + set: function() { + assertUnreachable('@@iterator should not be set'); + } + }); + assertArrayLikeEquals(constructor.from(obj), [1, 2, 3], constructor); + assertEquals(getCalled, 1); + assertEquals(called, 1); + + assertEquals(constructor, Uint8Array.from.call(constructor, [1]).constructor); + assertEquals(Uint8Array, constructor.from.call(Uint8Array, [1]).constructor); +}