Bug 1117350 - Part 2: Implement %TypedArray%.prototype.{reduce, reduceRight}. r=evilpie
authorziyunfei <446240525@qq.com>
Wed, 07 Jan 2015 01:55:00 -0500
changeset 222775 3b29b9cdedfc4fa4f8b1fc9f4fda385b98905968
parent 222774 f09142ac24fce82b545cc6fd09cd4b38c034dd55
child 222776 79d541cf6db233801f6f1714416f94b4c20ca844
push id10716
push userkwierso@gmail.com
push dateFri, 09 Jan 2015 01:17:28 +0000
treeherderfx-team@0f98d51a4a49 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie
bugs1117350
milestone37.0a1
Bug 1117350 - Part 2: Implement %TypedArray%.prototype.{reduce, reduceRight}. r=evilpie
js/src/builtin/TypedArray.js
js/src/tests/ecma_6/TypedArray/reduce-and-reduceRight.js
js/src/vm/TypedArrayObject.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -281,16 +281,94 @@ function TypedArrayLastIndexOf(searchEle
         if (O[k] === searchElement)
             return k;
     }
 
     // Step 12.
     return -1;
 }
 
+// ES6 draft rev30 (2014/12/24) 22.2.3.19 %TypedArray%.prototype.reduce(callbackfn[, initialValue]).
+function TypedArrayReduce(callbackfn/*, initialValue*/) {
+    // This function is not generic.
+    if (!IsObject(this) || !IsTypedArray(this))
+        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, "TypedArrayReduce");
+
+    // Steps 1-2.
+    var O = this;
+
+    // Steps 3-5.
+    var len = TypedArrayLength(O);
+
+    // Step 6.
+    if (arguments.length === 0)
+        ThrowError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.reduce");
+    if (!IsCallable(callbackfn))
+        ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+    // Step 7.
+    if (len === 0 && arguments.length === 1)
+        ThrowError(JSMSG_EMPTY_ARRAY_REDUCE);
+
+    // Step 8.
+    var k = 0;
+
+    // Steps 9-10.
+    // Omit some steps, since 'accumulator' should always be O[0] in step 10 for typed arrays.
+    var accumulator = arguments.length > 1 ? arguments[1] : O[k++];
+
+    // Step 11.
+    // Omit steps 11.b-11.c and the 'if' clause in step 11.d, since there are no holes in typed arrays.
+    for (; k < len; k++) {
+        accumulator = callFunction(callbackfn, undefined, accumulator, O[k], k, O);
+    }
+
+    // Step 12.
+    return accumulator;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.20 %TypedArray%.prototype.reduceRight(callbackfn[, initialValue]).
+function TypedArrayReduceRight(callbackfn/*, initialValue*/) {
+    // This function is not generic.
+    if (!IsObject(this) || !IsTypedArray(this))
+        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, "TypedArrayReduceRight");
+
+    // Steps 1-2.
+    var O = this;
+
+    // Steps 3-5.
+    var len = TypedArrayLength(O);
+
+    // Step 6.
+    if (arguments.length === 0)
+        ThrowError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.reduceRight");
+    if (!IsCallable(callbackfn))
+        ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+    // Step 7.
+    if (len === 0 && arguments.length === 1)
+        ThrowError(JSMSG_EMPTY_ARRAY_REDUCE);
+
+    // Step 8.
+    var k = len - 1;
+
+    // Steps 9-10.
+    // Omit some steps, since 'accumulator' should always be O[len-1] in step 10 for typed arrays.
+    var accumulator = arguments.length > 1 ? arguments[1] : O[k--];
+
+    // Step 11.
+    // Omit steps 11.b-11.c and the 'if' clause in step 11.d, since there are no holes in typed arrays.
+    for (; k >= 0; k--) {
+        accumulator = callFunction(callbackfn, undefined, accumulator, O[k], k, O);
+    }
+
+    // Step 12.
+    return accumulator;
+}
+
 // ES6 draft rev29 (2014/12/06) 22.2.3.21 %TypedArray%.prototype.reverse().
 function TypedArrayReverse() {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, "TypedArrayReverse");
     }
 
     // Steps 1-2.
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/reduce-and-reduceRight.js
@@ -0,0 +1,202 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+// Tests for TypedArray#reduce.
+for (var constructor of constructors) {
+    assertEq(constructor.prototype.reduce.length, 1);
+
+    // Basic tests.
+    var arr = new constructor([1, 2, 3, 4, 5]);
+
+    assertEq(arr.reduce((previous, current) => previous + current), 15);
+    assertEq(arr.reduce((previous, current) => current - previous), 3);
+
+    var count = 0;
+    var sum = 0;
+    assertEq(arr.reduce((previous, current, index, array) => {
+        count++;
+        sum += current;
+        assertEq(current - 1, index);
+        assertEq(current, arr[index]);
+        assertEq(array, arr);
+        return previous * current;
+    }), 120);
+    assertEq(count, 4);
+    assertEq(sum, 14);
+
+    // Tests for `initialValue` argument.
+    assertEq(arr.reduce((previous, current) => previous + current, -15), 0);
+    assertEq(arr.reduce((previous, current) => previous + current, ""), "12345");
+    assertDeepEq(arr.reduce((previous, current) => previous.concat(current), []), [1, 2, 3, 4, 5]);
+
+    // Tests for `this` value.
+    var global = this;
+    arr.reduce(function(){
+        assertEq(this, global);
+    });
+    arr.reduce(function(){
+        "use strict";
+        assertEq(this, undefined);
+    });
+    arr.reduce(() => assertEq(this, global));
+
+    // Throw an exception in the callback.
+    var count = 0;
+    var sum = 0;
+    assertThrowsInstanceOf(() => {
+        arr.reduce((previous, current, index, array) => {
+            count++;
+            sum += current;
+            if (index === 3) {
+                throw TypeError("reduce");
+            }
+        })
+    }, TypeError);
+    assertEq(count, 3);
+    assertEq(sum, 9);
+
+    // There is no callback or callback is not a function.
+    assertThrowsInstanceOf(() => {
+        arr.reduce();
+    }, TypeError);
+    var invalidCallbacks = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidCallbacks.forEach(callback => {
+        assertThrowsInstanceOf(() => {
+            arr.reduce(callback);
+        }, TypeError);
+    })
+
+    // Callback is a generator.
+    arr.reduce(function*(){
+        throw "This line will not be executed";
+    });
+
+    // Called from other globals.
+    if (typeof newGlobal === "function") {
+        var reduce = newGlobal()[constructor.name].prototype.reduce;
+        assertEq(reduce.call(arr, (previous, current) => Math.min(previous, current)), 1);
+    }
+
+    // Throws if `this` isn't a TypedArray.
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidReceivers.forEach(invalidReceiver => {
+        assertThrowsInstanceOf(() => {
+            constructor.prototype.reduce.call(invalidReceiver, () => {});
+        }, TypeError, "Assert that reduce fails if this value is not a TypedArray");
+    });
+    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
+    constructor.prototype.reduce.call(new Proxy(new constructor(3), {}), () => {});
+
+    // Test that the length getter is never called.
+    assertEq(Object.defineProperty(arr, "length", {
+        get() {
+            throw new Error("length accessor called");
+        }
+    }).reduce((previous, current) => Math.max(previous, current)), 5);
+}
+
+// Tests for TypedArray#reduceRight.
+for (var constructor of constructors) {
+    assertEq(constructor.prototype.reduceRight.length, 1);
+
+    // Basic tests.
+    var arr = new constructor([1, 2, 3, 4, 5]);
+
+    assertEq(arr.reduceRight((previous, current) => previous + current), 15);
+    assertEq(arr.reduceRight((previous, current) => current - previous), 3);
+
+    var count = 0;
+    var sum = 0;
+    assertEq(arr.reduceRight((previous, current, index, array) => {
+        count++;
+        sum += current;
+        assertEq(current - 1, index);
+        assertEq(current, arr[index]);
+        assertEq(array, arr);
+        return previous * current;
+    }), 120);
+    assertEq(count, 4);
+    assertEq(sum, 10);
+
+    // Tests for `initialValue` argument.
+    assertEq(arr.reduceRight((previous, current) => previous + current, -15), 0);
+    assertEq(arr.reduceRight((previous, current) => previous + current, ""), "54321");
+    assertDeepEq(arr.reduceRight((previous, current) => previous.concat(current), []), [5, 4, 3, 2, 1]);
+
+    // Tests for `this` value.
+    var global = this;
+    arr.reduceRight(function(){
+        assertEq(this, global);
+    });
+    arr.reduceRight(function(){
+        "use strict";
+        assertEq(this, undefined);
+    });
+    arr.reduceRight(() => assertEq(this, global));
+
+    // Throw an exception in the callback.
+    var count = 0;
+    var sum = 0;
+    assertThrowsInstanceOf(() => {
+        arr.reduceRight((previous, current, index, array) => {
+            count++;
+            sum += current;
+            if (index === 1) {
+                throw TypeError("reduceRight");
+            }
+        })
+    }, TypeError);
+    assertEq(count, 3);
+    assertEq(sum, 9);
+
+    // There is no callback or callback is not a function.
+    assertThrowsInstanceOf(() => {
+        arr.reduceRight();
+    }, TypeError);
+    var invalidCallbacks = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidCallbacks.forEach(callback => {
+        assertThrowsInstanceOf(() => {
+            arr.reduceRight(callback);
+        }, TypeError);
+    })
+
+    // Callback is a generator.
+    arr.reduceRight(function*(){
+        throw "This line will not be executed";
+    });
+
+    // Called from other globals.
+    if (typeof newGlobal === "function") {
+        var reduceRight = newGlobal()[constructor.name].prototype.reduceRight;
+        assertEq(reduceRight.call(arr, (previous, current) => Math.min(previous, current)), 1);
+    }
+
+    // Throws if `this` isn't a TypedArray.
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidReceivers.forEach(invalidReceiver => {
+        assertThrowsInstanceOf(() => {
+            constructor.prototype.reduceRight.call(invalidReceiver, () => {});
+        }, TypeError, "Assert that reduceRight fails if this value is not a TypedArray");
+    });
+    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
+    constructor.prototype.reduceRight.call(new Proxy(new constructor(3), {}), () => {});
+
+    // Test that the length getter is never called.
+    assertEq(Object.defineProperty(arr, "length", {
+        get() {
+            throw new Error("length accessor called");
+        }
+    }).reduceRight((previous, current) => Math.max(previous, current)), 5);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
\ No newline at end of file
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -785,16 +785,18 @@ TypedArrayObject::protoFunctions[] = {
     JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0),
     JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 2, 0),
     JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0),
     JS_SELF_HOSTED_FN("find", "TypedArrayFind", 2, 0),
     JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 2, 0),
     JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0),
     JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0),
     JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 2, 0),
+    JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0),
+    JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0),
     JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
     JS_SELF_HOSTED_FN("some", "TypedArraySome", 2, 0),
 #ifdef NIGHTLY_BUILD
     JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0),
 #endif
     JS_FS_END
 };
 
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -174,17 +174,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     gPrototypeProperties['Array'].push('includes');
   }
   for (var c of typedArrayClasses) {
     gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"];
   }
   gPrototypeProperties['TypedArray'] =
     ["length", "buffer", "byteLength", "byteOffset", kIteratorSymbol, "subarray",
      "set", "copyWithin", "find", "findIndex", "indexOf", "lastIndexOf", "reverse",
-     "join", "every", "some"];
+     "join", "every", "some", "reduce", "reduceRight"];
   if (isNightlyBuild) {
     gPrototypeProperties['TypedArray'].push('includes');
   }
   for (var c of errorObjectClasses) {
       gPrototypeProperties[c] = ["constructor", "name",
                                  // We don't actually resolve these empty data properties
                                  // onto the Xray prototypes, but we list them here to make
                                  // the test happy.