Implement %TypedArray%.prototype.{map,filter,some,reduce,reduceRight}
authordehrenberg <dehrenberg@chromium.org>
Tue, 19 May 2015 16:32:48 +0000 (09:32 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 19 May 2015 16:32:44 +0000 (16:32 +0000)
This patch adds implementations for additional TypedArray methods
from the ES6 spec, together with tests adapted from array code.

R=arv@chromium.org
BUG=v8:3578
LOG=Y

Review URL: https://codereview.chromium.org/1139663005

Cr-Commit-Position: refs/heads/master@{#28488}

src/array.js
src/harmony-typedarray.js
test/mjsunit/harmony/typedarray-iteration.js [new file with mode: 0644]
test/mjsunit/harmony/typedarray-reduce.js [new file with mode: 0644]

index 93378cf..a7c37e1 100644 (file)
@@ -12,6 +12,11 @@ var $arraySplice;
 var $arrayUnshift;
 var $innerArrayForEach;
 var $innerArrayEvery;
+var $innerArrayFilter;
+var $innerArrayMap;
+var $innerArrayReduce;
+var $innerArrayReduceRight;
+var $innerArraySome;
 
 (function(global, shared, exports) {
 
@@ -1150,14 +1155,7 @@ function ArraySort(comparefn) {
 // The following functions cannot be made efficient on sparse arrays while
 // preserving the semantics, since the calls to the receiver function can add
 // or delete elements from the array.
-function ArrayFilter(f, receiver) {
-  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter");
-
-  // Pull out the length so that modifications to the length in the
-  // loop will not affect the looping and side effects are visible.
-  var array = $toObject(this);
-  var length = $toUint32(array.length);
-
+function InnerArrayFilter(f, receiver, array, length) {
   if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
   var needs_wrapper = false;
   if (IS_NULL(receiver)) {
@@ -1186,6 +1184,17 @@ function ArrayFilter(f, receiver) {
   return result;
 }
 
+function ArrayFilter(f, receiver) {
+  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter");
+
+  // Pull out the length so that modifications to the length in the
+  // loop will not affect the looping and side effects are visible.
+  var array = $toObject(this);
+  var length = $toUint32(array.length);
+
+  return InnerArrayFilter(f, receiver, array, length);
+}
+
 function InnerArrayForEach(f, receiver, array, length) {
   if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
   var needs_wrapper = false;
@@ -1219,16 +1228,7 @@ function ArrayForEach(f, receiver) {
 }
 
 
-// Executes the function once for each element present in the
-// array until it finds one where callback returns true.
-function ArraySome(f, receiver) {
-  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some");
-
-  // Pull out the length so that modifications to the length in the
-  // loop will not affect the looping and side effects are visible.
-  var array = $toObject(this);
-  var length = TO_UINT32(array.length);
-
+function InnerArraySome(f, receiver, array, length) {
   if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
   var needs_wrapper = false;
   if (IS_NULL(receiver)) {
@@ -1252,6 +1252,19 @@ function ArraySome(f, receiver) {
 }
 
 
+// Executes the function once for each element present in the
+// array until it finds one where callback returns true.
+function ArraySome(f, receiver) {
+  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some");
+
+  // Pull out the length so that modifications to the length in the
+  // loop will not affect the looping and side effects are visible.
+  var array = $toObject(this);
+  var length = TO_UINT32(array.length);
+  return InnerArraySome(f, receiver, array, length);
+}
+
+
 function InnerArrayEvery(f, receiver, array, length) {
   if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
   var needs_wrapper = false;
@@ -1286,14 +1299,7 @@ function ArrayEvery(f, receiver) {
 }
 
 
-function ArrayMap(f, receiver) {
-  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
-
-  // Pull out the length so that modifications to the length in the
-  // loop will not affect the looping and side effects are visible.
-  var array = $toObject(this);
-  var length = TO_UINT32(array.length);
-
+function InnerArrayMap(f, receiver, array, length) {
   if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
   var needs_wrapper = false;
   if (IS_NULL(receiver)) {
@@ -1320,6 +1326,17 @@ function ArrayMap(f, receiver) {
 }
 
 
+function ArrayMap(f, receiver) {
+  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
+
+  // Pull out the length so that modifications to the length in the
+  // loop will not affect the looping and side effects are visible.
+  var array = $toObject(this);
+  var length = TO_UINT32(array.length);
+  return InnerArrayMap(f, receiver, array, length);
+}
+
+
 function ArrayIndexOf(element, index) {
   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.indexOf");
 
@@ -1430,21 +1447,14 @@ function ArrayLastIndexOf(element, index) {
 }
 
 
-function ArrayReduce(callback, current) {
-  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce");
-
-  // Pull out the length so that modifications to the length in the
-  // loop will not affect the looping and side effects are visible.
-  var array = $toObject(this);
-  var length = $toUint32(array.length);
-
+function InnerArrayReduce(callback, current, array, length, argumentsLength) {
   if (!IS_SPEC_FUNCTION(callback)) {
     throw MakeTypeError(kCalledNonCallable, callback);
   }
 
   var is_array = IS_ARRAY(array);
   var i = 0;
-  find_initial: if (%_ArgumentsLength() < 2) {
+  find_initial: if (argumentsLength < 2) {
     for (; i < length; i++) {
       if (HAS_INDEX(array, i, is_array)) {
         current = array[i++];
@@ -1467,21 +1477,27 @@ function ArrayReduce(callback, current) {
 }
 
 
-function ArrayReduceRight(callback, current) {
-  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight");
+function ArrayReduce(callback, current) {
+  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce");
 
-  // Pull out the length so that side effects are visible before the
-  // callback function is checked.
+  // Pull out the length so that modifications to the length in the
+  // loop will not affect the looping and side effects are visible.
   var array = $toObject(this);
   var length = $toUint32(array.length);
+  return InnerArrayReduce(callback, current, array, length,
+                          %_ArgumentsLength());
+}
 
+
+function InnerArrayReduceRight(callback, current, array, length,
+                               argumentsLength) {
   if (!IS_SPEC_FUNCTION(callback)) {
     throw MakeTypeError(kCalledNonCallable, callback);
   }
 
   var is_array = IS_ARRAY(array);
   var i = length - 1;
-  find_initial: if (%_ArgumentsLength() < 2) {
+  find_initial: if (argumentsLength < 2) {
     for (; i >= 0; i--) {
       if (HAS_INDEX(array, i, is_array)) {
         current = array[i--];
@@ -1503,6 +1519,18 @@ function ArrayReduceRight(callback, current) {
   return current;
 }
 
+
+function ArrayReduceRight(callback, current) {
+  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight");
+
+  // Pull out the length so that side effects are visible before the
+  // callback function is checked.
+  var array = $toObject(this);
+  var length = $toUint32(array.length);
+  return InnerArrayReduceRight(callback, current, array, length,
+                               %_ArgumentsLength());
+}
+
 // ES5, 15.4.3.2
 function ArrayIsArray(obj) {
   return IS_ARRAY(obj);
@@ -1607,7 +1635,12 @@ $arraySlice = ArraySlice;
 $arraySplice = ArraySplice;
 $arrayUnshift = ArrayUnshift;
 
-$innerArrayForEach = InnerArrayForEach;
 $innerArrayEvery = InnerArrayEvery;
+$innerArrayFilter = InnerArrayFilter;
+$innerArrayForEach = InnerArrayForEach;
+$innerArrayMap = InnerArrayMap;
+$innerArrayReduce = InnerArrayReduce;
+$innerArrayReduceRight = InnerArrayReduceRight;
+$innerArraySome = InnerArraySome;
 
 });
index 90679e0..b1115f4 100644 (file)
@@ -30,6 +30,28 @@ DECLARE_GLOBALS(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 ConstructTypedArrayLike(typedArray, arrayContents) {
+  // TODO(littledan): The spec requires that we actuallly use
+  // typedArray.constructor[Symbol.species] (bug v8:4093)
+  return new typedArray.constructor(arrayContents);
+}
+
 function TypedArrayCopyWithin(target, start, end) {
   if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
 
@@ -61,7 +83,7 @@ function TypedArrayForEach(f, receiver) {
 %FunctionSetLength(TypedArrayForEach, 1);
 
 // ES6 draft 04-05-14 section 22.2.3.8
-function TypedArrayFill(value, start , end) {
+function TypedArrayFill(value, start, end) {
   if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
 
   var length = %_TypedArrayGetLength(this);
@@ -70,6 +92,16 @@ function TypedArrayFill(value, start , end) {
 }
 %FunctionSetLength(TypedArrayFill, 1);
 
+// ES6 draft 07-15-13, section 22.2.3.9
+function TypedArrayFilter(predicate, thisArg) {
+  if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
+
+  var length = %_TypedArrayGetLength(this);
+  var array = $innerArrayFilter(predicate, thisArg, this, length);
+  return ConstructTypedArrayLike(this, array);
+}
+%FunctionSetLength(TypedArrayFilter, 1);
+
 // ES6 draft 07-15-13, section 22.2.3.10
 function TypedArrayFind(predicate, thisArg) {
   if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
@@ -91,6 +123,52 @@ function TypedArrayFindIndex(predicate, thisArg) {
 %FunctionSetLength(TypedArrayFindIndex, 1);
 
 
+// ES6 draft 07-15-13, section 22.2.3.18
+function TypedArrayMap(predicate, thisArg) {
+  if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
+
+  // TODO(littledan): Preallocate rather than making an intermediate
+  // array, for better performance.
+  var length = %_TypedArrayGetLength(this);
+  var array = $innerArrayMap(predicate, thisArg, this, length);
+  return ConstructTypedArrayLike(this, array);
+}
+%FunctionSetLength(TypedArrayMap, 1);
+
+
+// ES6 draft 07-15-13, section 22.2.3.19
+function TypedArrayReduce(callback, current) {
+  if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
+
+  var length = %_TypedArrayGetLength(this);
+  return $innerArrayReduce(callback, current, this, length,
+                           %_ArgumentsLength());
+}
+%FunctionSetLength(TypedArrayReduce, 1);
+
+
+// ES6 draft 07-15-13, section 22.2.3.19
+function TypedArrayReduceRight(callback, current) {
+  if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
+
+  var length = %_TypedArrayGetLength(this);
+  return $innerArrayReduceRight(callback, current, this, length,
+                                %_ArgumentsLength());
+}
+%FunctionSetLength(TypedArrayReduceRight, 1);
+
+
+// ES6 draft 05-05-15, section 22.2.3.24
+function TypedArraySome(f, receiver) {
+  if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray);
+
+  var length = %_TypedArrayGetLength(this);
+
+  return $innerArraySome(f, receiver, this, length);
+}
+%FunctionSetLength(TypedArraySome, 1);
+
+
 // ES6 draft 08-24-14, section 22.2.2.2
 function TypedArrayOf() {
   var length = %_ArgumentsLength();
@@ -137,10 +215,15 @@ macro EXTEND_TYPED_ARRAY(NAME)
   $installFunctions(GlobalNAME.prototype, DONT_ENUM, [
     "copyWithin", TypedArrayCopyWithin,
     "every", TypedArrayEvery,
-    "forEach", TypedArrayForEach,
+    "fill", TypedArrayFill,
+    "filter", TypedArrayFilter,
     "find", TypedArrayFind,
     "findIndex", TypedArrayFindIndex,
-    "fill", TypedArrayFill
+    "forEach", TypedArrayForEach,
+    "map", TypedArrayMap,
+    "reduce", TypedArrayReduce,
+    "reduceRight", TypedArrayReduceRight,
+    "some", TypedArraySome
   ]);
 endmacro
 
diff --git a/test/mjsunit/harmony/typedarray-iteration.js b/test/mjsunit/harmony/typedarray-iteration.js
new file mode 100644 (file)
index 0000000..85c5091
--- /dev/null
@@ -0,0 +1,196 @@
+// 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
+
+// Tests for standard TypedArray array iteration functions.
+
+var typedArrayConstructors = [
+  Uint8Array,
+  Int8Array,
+  Uint16Array,
+  Int16Array,
+  Uint32Array,
+  Int32Array,
+  Uint8ClampedArray,
+  Float32Array,
+  Float64Array
+];
+
+function assertArrayLikeEquals(expected, value, type) {
+  assertEquals(value.__proto__, type.prototype);
+  assertEquals(expected.length, value.length);
+  for (var i = 0; i < value.length; ++i) {
+    assertEquals(expected[i], value[i]);
+  }
+}
+
+for (var constructor of typedArrayConstructors) {
+  (function TypedArrayFilterTest() {
+    // Simple use.
+    var a = new constructor([0, 1]);
+    assertArrayLikeEquals([0], a.filter(function(n) { return n == 0; }),
+                          constructor);
+    assertArrayLikeEquals([0, 1], a, constructor);
+
+    // Use specified object as this object when calling the function.
+    var o = { value: 42 }
+    a = new constructor([1, 42, 3, 42, 4]);
+    assertArrayLikeEquals([42, 42], a.filter(function(n) {
+      return this.value == n
+    }, o), constructor);
+
+    // Modify original array.
+    a = new constructor([1, 42, 3, 42, 4]);
+    assertArrayLikeEquals([42, 42], a.filter(function(n, index, array) {
+      array[index] = 43; return 42 == n;
+    }), constructor);
+    assertArrayLikeEquals([43, 43, 43, 43, 43], a, constructor);
+
+    // Create a new object in each function call when receiver is a
+    // primitive value. See ECMA-262, Annex C.
+    a = [];
+    new constructor([1, 2]).filter(function() { a.push(this) }, '');
+    assertTrue(a[0] !== a[1]);
+
+    // Do not create a new object otherwise.
+    a = [];
+    new constructor([1, 2]).filter(function() { a.push(this) }, {});
+    assertEquals(a[0], a[1]);
+
+    // In strict mode primitive values should not be coerced to an object.
+    a = [];
+    new constructor([1, 2]).filter(function() {
+      'use strict';
+      a.push(this);
+    }, '');
+    assertEquals('', a[0]);
+    assertEquals(a[0], a[1]);
+
+    // Calling this method on other types is a TypeError
+    assertThrows(function() {
+      constructor.prototype.filter.call([], function() {});
+    }, TypeError);
+
+    // Shadowing the length property doesn't change anything
+    a = new constructor([1, 2]);
+    Object.defineProperty(a, 'length', { value: 1 });
+    assertArrayLikeEquals([2], a.filter(function(elt) {
+      return elt == 2;
+    }), constructor);
+  })();
+
+  (function TypedArrayMapTest() {
+    var a = new constructor([0, 1, 2, 3, 4]);
+
+    // Simple use.
+    var result = [1, 2, 3, 4, 5];
+    assertArrayLikeEquals(result, a.map(function(n) { return n + 1; }),
+                          constructor);
+    assertEquals(a, a);
+
+    // Use specified object as this object when calling the function.
+    var o = { delta: 42 }
+    result = [42, 43, 44, 45, 46];
+    assertArrayLikeEquals(result, a.map(function(n) {
+      return this.delta + n;
+    }, o), constructor);
+
+    // Modify original array.
+    a = new constructor([0, 1, 2, 3, 4]);
+    result = [1, 2, 3, 4, 5];
+    assertArrayLikeEquals(result, a.map(function(n, index, array) {
+      array[index] = n + 1;
+      return n + 1;
+    }), constructor);
+    assertArrayLikeEquals(result, a, constructor);
+
+    // Create a new object in each function call when receiver is a
+    // primitive value. See ECMA-262, Annex C.
+    a = [];
+    new constructor([1, 2]).map(function() { a.push(this) }, '');
+    assertTrue(a[0] !== a[1]);
+
+    // Do not create a new object otherwise.
+    a = [];
+    new constructor([1, 2]).map(function() { a.push(this) }, {});
+    assertEquals(a[0], a[1]);
+
+    // In strict mode primitive values should not be coerced to an object.
+    a = [];
+    new constructor([1, 2]).map(function() { 'use strict'; a.push(this); }, '');
+    assertEquals('', a[0]);
+    assertEquals(a[0], a[1]);
+
+    // Test that the result is converted to the right type
+    assertArrayLikeEquals([3, 3], new constructor([1, 2]).map(function() {
+      return "3";
+    }), constructor);
+    if (constructor !== Float32Array && constructor !== Float64Array) {
+      assertArrayLikeEquals([0, 0], new constructor([1, 2]).map(function() {
+        return NaN;
+      }), constructor);
+    }
+  })();
+
+  //
+  // %TypedArray%.prototype.some
+  //
+  (function TypedArraySomeTest() {
+    var a = new constructor([0, 1, 2, 3, 4]);
+
+    // Simple use.
+    assertTrue(a.some(function(n) { return n == 3}));
+    assertFalse(a.some(function(n) { return n == 5}));
+
+    // Use specified object as this object when calling the function.
+    var o = { element: 42 };
+    a = new constructor([1, 42, 3]);
+    assertTrue(a.some(function(n) { return this.element == n; }, o));
+    a = new constructor([1]);
+    assertFalse(a.some(function(n) { return this.element == n; }, o));
+
+    // Modify original array.
+    a = new constructor([0, 1, 2, 3]);
+    assertTrue(a.some(function(n, index, array) {
+      array[index] = n + 1;
+      return n == 2;
+    }));
+    assertArrayLikeEquals([1, 2, 3, 3], a, constructor);
+
+    // Create a new object in each function call when receiver is a
+    // primitive value. See ECMA-262, Annex C.
+    a = [];
+    new constructor([1, 2]).some(function() { a.push(this) }, '');
+    assertTrue(a[0] !== a[1]);
+
+    // Do not create a new object otherwise.
+    a = [];
+    new constructor([1, 2]).some(function() { a.push(this) }, {});
+    assertEquals(a[0], a[1]);
+
+    // In strict mode primitive values should not be coerced to an object.
+    a = [];
+    new constructor([1, 2]).some(function() {
+      'use strict';
+      a.push(this);
+    }, '');
+    assertEquals('', a[0]);
+    assertEquals(a[0], a[1]);
+
+    // Calling this method on other types is a TypeError
+    assertThrows(function() {
+      constructor.prototype.some.call([], function() {});
+    }, TypeError);
+
+    // Shadowing the length property doesn't change anything
+    a = new constructor([1, 2]);
+    Object.defineProperty(a, 'length', { value: 1 });
+    assertEquals(true, a.some(function(elt) { return elt == 2; }));
+    assertEquals(false, Array.prototype.some.call(a, function(elt) {
+      return elt == 2;
+    }));
+  })();
+
+}
diff --git a/test/mjsunit/harmony/typedarray-reduce.js b/test/mjsunit/harmony/typedarray-reduce.js
new file mode 100644 (file)
index 0000000..15bb0ba
--- /dev/null
@@ -0,0 +1,256 @@
+// 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 --allow-natives-syntax
+
+var typedArrayConstructors = [
+  Uint8Array,
+  Int8Array,
+  Uint16Array,
+  Int16Array,
+  Uint32Array,
+  Int32Array,
+  Uint8ClampedArray,
+  Float32Array,
+  Float64Array
+];
+
+function clone(v) {
+  // Shallow-copies arrays, returns everything else verbatim.
+  if (v instanceof Array) {
+    // Shallow-copy an array.
+    var newArray = new Array(v.length);
+    for (var i in v) {
+      newArray[i] = v[i];
+    }
+    return newArray;
+  }
+  return v;
+}
+
+
+// Creates a callback function for reduce/reduceRight that tests the number
+// of arguments and otherwise behaves as "func", but which also
+// records all calls in an array on the function (as arrays of arguments
+// followed by result).
+function makeRecorder(func, testName) {
+  var record = [];
+  var f = function recorder(a, b, i, s) {
+    assertEquals(4, arguments.length,
+                 testName + "(number of arguments: " + arguments.length + ")");
+    assertEquals("number", typeof(i), testName + "(index must be number)");
+    assertEquals(s[i], b, testName + "(current argument is at index)");
+    if (record.length > 0) {
+      var prevRecord = record[record.length - 1];
+      var prevResult = prevRecord[prevRecord.length - 1];
+      assertEquals(prevResult, a,
+                   testName + "(prev result -> current input)");
+    }
+    var args = [clone(a), clone(b), i, clone(s)];
+    var result = func.apply(this, arguments);
+    args.push(clone(result));
+    record.push(args);
+    return result;
+  };
+  f.record = record;
+  return f;
+}
+
+
+function testReduce(type,
+                    testName,
+                    expectedResult,
+                    expectedCalls,
+                    array,
+                    combine,
+                    init) {
+  var rec = makeRecorder(combine);
+  var result;
+  var performsCall;
+  if (arguments.length > 6) {
+    result = array[type](rec, init);
+  } else {
+    result = array[type](rec);
+  }
+  var calls = rec.record;
+  assertEquals(expectedCalls.length, calls.length,
+               testName + " (number of calls)");
+  for (var i = 0; i < expectedCalls.length; i++) {
+    assertEquals(expectedCalls[i], calls[i],
+                 testName + " (call " + (i + 1) + ")");
+  }
+  assertEquals(expectedResult, result, testName + " (result)");
+}
+
+
+function sum(a, b) { return a + b; }
+function prod(a, b) { return a * b; }
+function dec(a, b, i, arr) { return a + b * Math.pow(10, arr.length - i - 1); }
+function accumulate(acc, elem, i) { acc[i] = elem; return acc; }
+
+for (var constructor of typedArrayConstructors) {
+  // ---- Test Reduce[Left]
+
+  var simpleArray = new constructor([2,4,6])
+
+  testReduce("reduce", "SimpleReduceSum", 12,
+             [[0, 2, 0, simpleArray, 2],
+              [2, 4, 1, simpleArray, 6],
+              [6, 6, 2, simpleArray, 12]],
+             simpleArray, sum, 0);
+
+  testReduce("reduce", "SimpleReduceProd", 48,
+             [[1, 2, 0, simpleArray, 2],
+              [2, 4, 1, simpleArray, 8],
+              [8, 6, 2, simpleArray, 48]],
+             simpleArray, prod, 1);
+
+  testReduce("reduce", "SimpleReduceDec", 246,
+             [[0, 2, 0, simpleArray, 200],
+              [200, 4, 1, simpleArray, 240],
+              [240, 6, 2, simpleArray, 246]],
+             simpleArray, dec, 0);
+
+  testReduce("reduce", "SimpleReduceAccumulate", [2, 4, 6],
+             [[[], 2, 0, simpleArray, [2]],
+              [[2], 4, 1, simpleArray, [2, 4]],
+              [[2,4], 6, 2, simpleArray, [2, 4, 6]]],
+             simpleArray, accumulate, []);
+
+
+  testReduce("reduce", "EmptyReduceSum", 0, [], [], sum, 0);
+  testReduce("reduce", "EmptyReduceProd", 1, [], [], prod, 1);
+  testReduce("reduce", "EmptyReduceDec", 0, [], [], dec, 0);
+  testReduce("reduce", "EmptyReduceAccumulate", [], [], [], accumulate, []);
+
+  testReduce("reduce", "EmptyReduceSumNoInit", 0, [], [0], sum);
+  testReduce("reduce", "EmptyReduceProdNoInit", 1, [], [1], prod);
+  testReduce("reduce", "EmptyReduceDecNoInit", 0, [], [0], dec);
+  testReduce("reduce", "EmptyReduceAccumulateNoInit", [], [], [[]], accumulate);
+
+  // ---- Test ReduceRight
+
+  testReduce("reduceRight", "SimpleReduceRightSum", 12,
+             [[0, 6, 2, simpleArray, 6],
+              [6, 4, 1, simpleArray, 10],
+              [10, 2, 0, simpleArray, 12]],
+             simpleArray, sum, 0);
+
+  testReduce("reduceRight", "SimpleReduceRightProd", 48,
+             [[1, 6, 2, simpleArray, 6],
+              [6, 4, 1, simpleArray, 24],
+              [24, 2, 0, simpleArray, 48]],
+             simpleArray, prod, 1);
+
+  testReduce("reduceRight", "SimpleReduceRightDec", 246,
+             [[0, 6, 2, simpleArray, 6],
+              [6, 4, 1, simpleArray, 46],
+              [46, 2, 0, simpleArray, 246]],
+             simpleArray, dec, 0);
+
+
+  testReduce("reduceRight", "EmptyReduceRightSum", 0, [], [], sum, 0);
+  testReduce("reduceRight", "EmptyReduceRightProd", 1, [], [], prod, 1);
+  testReduce("reduceRight", "EmptyReduceRightDec", 0, [], [], dec, 0);
+  testReduce("reduceRight", "EmptyReduceRightAccumulate", [],
+             [], [], accumulate, []);
+
+  testReduce("reduceRight", "EmptyReduceRightSumNoInit", 0, [], [0], sum);
+  testReduce("reduceRight", "EmptyReduceRightProdNoInit", 1, [], [1], prod);
+  testReduce("reduceRight", "EmptyReduceRightDecNoInit", 0, [], [0], dec);
+  testReduce("reduceRight", "EmptyReduceRightAccumulateNoInit",
+             [], [], [[]], accumulate);
+
+  // Ignore non-array properties:
+
+  var arrayPlus = [1,2,3];
+  arrayPlus[-1] = NaN;
+  arrayPlus[Math.pow(2,32)] = NaN;
+  arrayPlus[NaN] = NaN;
+  arrayPlus["00"] = NaN;
+  arrayPlus["02"] = NaN;
+  arrayPlus["-0"] = NaN;
+
+  testReduce("reduce", "ArrayWithNonElementPropertiesReduce", 6,
+             [[0, 1, 0, arrayPlus, 1],
+              [1, 2, 1, arrayPlus, 3],
+              [3, 3, 2, arrayPlus, 6],
+             ], arrayPlus, sum, 0);
+
+  testReduce("reduceRight", "ArrayWithNonElementPropertiesReduceRight", 6,
+             [[0, 3, 2, arrayPlus, 3],
+              [3, 2, 1, arrayPlus, 5],
+              [5, 1, 0, arrayPlus, 6],
+             ], arrayPlus, sum, 0);
+
+
+  // Test error conditions:
+
+  var exception = false;
+  try {
+    new constructor([1]).reduce("not a function");
+  } catch (e) {
+    exception = true;
+    assertTrue(e instanceof TypeError,
+               "reduce callback not a function not throwing TypeError");
+    assertTrue(e.message.indexOf(" is not a function") >= 0,
+               "reduce non function TypeError type");
+  }
+  assertTrue(exception);
+
+  exception = false;
+  try {
+    new constructor([1]).reduceRight("not a function");
+  } catch (e) {
+    exception = true;
+    assertTrue(e instanceof TypeError,
+               "reduceRight callback not a function not throwing TypeError");
+    assertTrue(e.message.indexOf(" is not a function") >= 0,
+               "reduceRight non function TypeError type");
+  }
+  assertTrue(exception);
+
+  exception = false;
+  try {
+    new constructor([]).reduce(sum);
+  } catch (e) {
+    exception = true;
+    assertTrue(e instanceof TypeError,
+               "reduce no initial value not throwing TypeError");
+    assertEquals("Reduce of empty array with no initial value", e.message,
+                 "reduce no initial TypeError type");
+  }
+  assertTrue(exception);
+
+  exception = false;
+  try {
+    new constructor([]).reduceRight(sum);
+  } catch (e) {
+    exception = true;
+    assertTrue(e instanceof TypeError,
+               "reduceRight no initial value not throwing TypeError");
+    assertEquals("Reduce of empty array with no initial value", e.message,
+                 "reduceRight no initial TypeError type");
+  }
+  assertTrue(exception);
+
+  // Reduce fails when called on non-TypedArrays
+  assertThrows(function() {
+    constructor.prototype.reduce.call([], function() {}, null);
+  }, TypeError);
+  assertThrows(function() {
+    constructor.prototype.reduceRight.call([], function() {}, null);
+  }, TypeError);
+
+  // Shadowing length doesn't affect every, unlike Array.prototype.every
+  var a = new constructor([1, 2]);
+  Object.defineProperty(a, 'length', {value: 1});
+  assertEquals(a.reduce(sum, 0), 3);
+  assertEquals(Array.prototype.reduce.call(a, sum, 0), 1);
+  assertEquals(a.reduceRight(sum, 0), 3);
+  assertEquals(Array.prototype.reduceRight.call(a, sum, 0), 1);
+
+  assertEquals(1, constructor.prototype.reduce.length);
+  assertEquals(1, constructor.prototype.reduceRight.length);
+}