Implement .forEach() on typed arrays

BUG=v8:3578
LOG=Y
R=dslomov@chromium.org, wingo@igalia.com

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

Patch from Adrian Perez de Castro <aperez@igalia.com>.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24657 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
diff --git a/BUILD.gn b/BUILD.gn
index 68b1a14..f736901 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -243,6 +243,7 @@
     "src/generator.js",
     "src/harmony-string.js",
     "src/harmony-array.js",
+    "src/harmony-typedarray.js",
     "src/harmony-classes.js",
   ]
 
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index 2dde719..1e453e5 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -2086,6 +2086,7 @@
     INSTALL_EXPERIMENTAL_NATIVE(i, proxies, "proxy.js")
     INSTALL_EXPERIMENTAL_NATIVE(i, strings, "harmony-string.js")
     INSTALL_EXPERIMENTAL_NATIVE(i, arrays, "harmony-array.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, arrays, "harmony-typedarray.js")
     INSTALL_EXPERIMENTAL_NATIVE(i, classes, "harmony-classes.js")
   }
 
diff --git a/src/harmony-typedarray.js b/src/harmony-typedarray.js
new file mode 100644
index 0000000..0129f38
--- /dev/null
+++ b/src/harmony-typedarray.js
@@ -0,0 +1,79 @@
+// Copyright 2014 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.
+
+'use strict';
+
+// This file relies on the fact that the following declaration has been made
+// in runtime.js:
+// var $Array = global.Array;
+
+// -------------------------------------------------------------------
+
+macro TYPED_ARRAYS(FUNCTION)
+// arrayIds below should be synchronized with Runtime_TypedArrayInitialize.
+FUNCTION(1, Uint8Array, 1)
+FUNCTION(2, Int8Array, 1)
+FUNCTION(3, Uint16Array, 2)
+FUNCTION(4, Int16Array, 2)
+FUNCTION(5, Uint32Array, 4)
+FUNCTION(6, Int32Array, 4)
+FUNCTION(7, Float32Array, 4)
+FUNCTION(8, Float64Array, 8)
+FUNCTION(9, Uint8ClampedArray, 1)
+endmacro
+
+
+macro TYPED_ARRAY_HARMONY_ADDITIONS(ARRAY_ID, NAME, ELEMENT_SIZE)
+
+// ES6 draft 08-24-14, section 22.2.3.12
+function NAMEForEach(f /* thisArg */) {  // length == 1
+  if (!%IsTypedArray(this)) {
+    throw MakeTypeError('not_typed_array', []);
+  }
+  if (!IS_SPEC_FUNCTION(f)) {
+    throw MakeTypeError('called_non_callable', [ f ]);
+  }
+
+  var length = %_TypedArrayGetLength(this);
+  var receiver;
+
+  if (%_ArgumentsLength() > 1) {
+    receiver = %_Arguments(1);
+  }
+
+  var needs_wrapper = false;
+  if (IS_NULL_OR_UNDEFINED(receiver)) {
+    receiver = %GetDefaultReceiver(f) || receiver;
+  } else {
+    needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver);
+  }
+
+  var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
+  for (var i = 0; i < length; i++) {
+    var element = this[i];
+    // Prepare break slots for debugger step in.
+    if (stepping) %DebugPrepareStepInIfStepping(f);
+    var new_receiver = needs_wrapper ? ToObject(receiver) : receiver;
+    %_CallFunction(new_receiver, TO_OBJECT_INLINE(element), i, this, f);
+  }
+}
+endmacro
+
+TYPED_ARRAYS(TYPED_ARRAY_HARMONY_ADDITIONS)
+
+
+function HarmonyTypedArrayExtendPrototypes() {
+macro EXTEND_TYPED_ARRAY(ARRAY_ID, NAME, ELEMENT_SIZE)
+  %CheckIsBootstrapping();
+
+  // Set up non-enumerable functions on the prototype object.
+  InstallFunctions(global.NAME.prototype, DONT_ENUM, $Array(
+    "forEach", NAMEForEach
+  ));
+endmacro
+
+  TYPED_ARRAYS(EXTEND_TYPED_ARRAY)
+}
+
+HarmonyTypedArrayExtendPrototypes();
diff --git a/src/runtime/runtime-typedarray.cc b/src/runtime/runtime-typedarray.cc
index c138a4f..d702ff9 100644
--- a/src/runtime/runtime-typedarray.cc
+++ b/src/runtime/runtime-typedarray.cc
@@ -500,6 +500,13 @@
 }
 
 
+RUNTIME_FUNCTION(Runtime_IsTypedArray) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 1);
+  return isolate->heap()->ToBoolean(args[0]->IsJSTypedArray());
+}
+
+
 RUNTIME_FUNCTION(Runtime_DataViewInitialize) {
   HandleScope scope(isolate);
   DCHECK(args.length() == 4);
diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h
index 747f25a..a3b1441 100644
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -351,6 +351,7 @@
   F(ArrayBufferIsView, 1, 1)                           \
   F(ArrayBufferNeuter, 1, 1)                           \
                                                        \
+  F(IsTypedArray, 1, 1)                                \
   F(TypedArrayInitializeFromArrayLike, 4, 1)           \
   F(TypedArrayGetBuffer, 1, 1)                         \
   F(TypedArraySetFastCases, 3, 1)                      \
diff --git a/test/mjsunit/harmony/typedarrays-foreach.js b/test/mjsunit/harmony/typedarrays-foreach.js
new file mode 100644
index 0000000..4bfa655
--- /dev/null
+++ b/test/mjsunit/harmony/typedarrays-foreach.js
@@ -0,0 +1,140 @@
+// Copyright 2014 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 CheckTypedArrayIsNeutered(array) {
+  assertEquals(0, array.byteLength);
+  assertEquals(0, array.byteOffset);
+  assertEquals(0, array.length);
+}
+
+function TestTypedArrayForEach(constructor) {
+  assertEquals(1, constructor.prototype.forEach.length);
+
+  var a = new constructor(2);
+  a[0] = 0;
+  a[1] = 1;
+
+  var count = 0;
+  a.forEach(function (n) { count++; });
+  assertEquals(2, count);
+
+  // Use specified object as this object when calling the function.
+  var o = { value: 42 };
+  var result = [];
+  a.forEach(function (n, index, array) { result.push(this.value); }, o);
+  assertArrayEquals([42, 42], result);
+
+  // Modify the original array.
+  count = 0;
+  a.forEach(function (n, index, array) { array[index] = n + 1; count++ });
+  assertEquals(2, count);
+  assertArrayEquals([1, 2], a);
+
+  // Check that values passed as second argument are wrapped into
+  // objects when calling into sloppy mode functions.
+  function CheckWrapping(value, wrapper) {
+    var wrappedValue = new wrapper(value);
+
+    a.forEach(function () {
+      assertEquals("object", typeof this);
+      assertEquals(wrappedValue, this);
+    }, value);
+
+    a.forEach(function () {
+      "use strict";
+      assertEquals(typeof value, typeof this);
+      assertEquals(value, this);
+    }, value);
+  }
+  CheckWrapping(true, Boolean);
+  CheckWrapping(false, Boolean);
+  CheckWrapping("xxx", String);
+  CheckWrapping(42, Number);
+  CheckWrapping(3.14, Number);
+  CheckWrapping({}, Object);
+
+  // Throw before completing iteration, only the first element
+  // should be modified when thorwing mid-way.
+  count = 0;
+  a[0] = 42;
+  a[1] = 42;
+  try {
+    a.forEach(function (n, index, array) {
+      if (count > 0) throw "meh";
+      array[index] = n + 1;
+      count++;
+    });
+  } catch (e) {
+  }
+  assertEquals(1, count);
+  assertEquals(43, a[0]);
+  assertEquals(42, a[1]);
+
+  // Neutering the buffer backing the typed array mid-way should
+  // still make .forEach() finish, and the array should keep being
+  // empty after neutering it.
+  count = 0;
+  a.forEach(function (n, index, array) {
+    if (count > 0) %ArrayBufferNeuter(array.buffer);
+    array[index] = n + 1;
+    count++;
+  });
+  assertEquals(2, count);
+  CheckTypedArrayIsNeutered(a);
+  assertEquals(undefined, a[0]);
+
+  // The method must work for typed arrays created from ArrayBuffer.
+  // The length of the ArrayBuffer is chosen so it is a multiple of
+  // all lengths of the typed array items.
+  a = new constructor(new ArrayBuffer(64));
+  count = 0;
+  a.forEach(function (n) { count++ });
+  assertEquals(a.length, count);
+
+  // Externalizing the array mid-way accessing the .buffer property
+  // should work.
+  a = new constructor(2);
+  count = 0;
+  var buffer = undefined;
+  a.forEach(function (n, index, array) {
+    if (count++ > 0)
+      buffer = array.buffer;
+  });
+  assertEquals(2, count);
+  assertTrue(!!buffer);
+  assertEquals("ArrayBuffer", %_ClassOf(buffer));
+  assertSame(buffer, a.buffer);
+
+  // The %TypedArray%.forEach() method should not work when
+  // transplanted to objects that are not typed arrays.
+  assertThrows(function () { constructor.prototype.forEach.call([1, 2, 3], function (x) {}) }, TypeError);
+  assertThrows(function () { constructor.prototype.forEach.call("abc", function (x) {}) }, TypeError);
+  assertThrows(function () { constructor.prototype.forEach.call({}, function (x) {}) }, TypeError);
+  assertThrows(function () { constructor.prototype.forEach.call(0, function (x) {}) }, TypeError);
+
+  // Method must be useable on instances of other typed arrays.
+  for (var i = 0; i < typedArrayConstructors.length; i++) {
+    count = 0;
+    a = new typedArrayConstructors[i](4);
+    constructor.prototype.forEach.call(a, function (x) { count++ });
+    assertEquals(a.length, count);
+  }
+}
+
+for (i = 0; i < typedArrayConstructors.length; i++) {
+  TestTypedArrayForEach(typedArrayConstructors[i]);
+}
diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp
index c80d6bc..cfec1b1 100644
--- a/tools/gyp/v8.gyp
+++ b/tools/gyp/v8.gyp
@@ -1604,6 +1604,7 @@
           '../../src/generator.js',
           '../../src/harmony-string.js',
           '../../src/harmony-array.js',
+          '../../src/harmony-typedarray.js',
           '../../src/harmony-classes.js',
         ],
         'libraries_bin_file': '<(SHARED_INTERMEDIATE_DIR)/libraries.bin',