Revision: 18113
Author:   [email protected]
Date:     Wed Nov 27 17:21:40 2013 UTC
Log:      Harmony promises

Based on prototype at

  https://github.com/rossberg-chromium/js-promise

which informed the latest spec draft version at

  https://github.com/domenic/promises-unwrapping/blob/master/README.md

Activated by --harmony-promises.

Feature complete with respect to the draft spec, plus the addition of .when and .deferred methods. Final naming and other possible deviations from the current draft will hopefully be resolved soon after the next TC39 meeting.

This CL also generalises the Object.observe delivery loop into a simplistic microtask loop. Currently, all observer events are delivered before invoking any promise handler in a single fixpoint iteration. It's not clear yet what the final semantics is supposed to be (should there be a global event ordering?), but it will probably require a more thorough event loop abstraction inside V8 once we get there.

[email protected], [email protected]
BUG=

Review URL: https://codereview.chromium.org/64223010
http://code.google.com/p/v8/source/detail?r=18113

Added:
 /branches/bleeding_edge/src/promise.js
 /branches/bleeding_edge/test/mjsunit/harmony/promises.js
Modified:
 /branches/bleeding_edge/src/api.cc
 /branches/bleeding_edge/src/bootstrapper.cc
 /branches/bleeding_edge/src/contexts.h
 /branches/bleeding_edge/src/execution.cc
 /branches/bleeding_edge/src/execution.h
 /branches/bleeding_edge/src/flag-definitions.h
 /branches/bleeding_edge/src/isolate.h
 /branches/bleeding_edge/src/messages.js
 /branches/bleeding_edge/src/object-observe.js
 /branches/bleeding_edge/src/objects.cc
 /branches/bleeding_edge/src/objects.h
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/runtime.h
 /branches/bleeding_edge/src/v8.cc
 /branches/bleeding_edge/src/v8natives.js
 /branches/bleeding_edge/test/mjsunit/harmony/iteration-semantics.js
 /branches/bleeding_edge/tools/gyp/v8.gyp

=======================================
--- /dev/null
+++ /branches/bleeding_edge/src/promise.js      Wed Nov 27 17:21:40 2013 UTC
@@ -0,0 +1,305 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"use strict";
+
+// This file relies on the fact that the following declaration has been made
+// in runtime.js:
+// var $Object = global.Object
+// var $WeakMap = global.WeakMap
+
+
+var $Promise = Promise;
+
+
+//-------------------------------------------------------------------
+
+// Core functionality.
+
+// Event queue format: [(value, [(handler, deferred)*])*]
+// I.e., a list of value/tasks pairs, where the value is a resolution value or +// rejection reason, and the tasks are a respective list of handler/deferred +// pairs waiting for notification of this value. Each handler is an onResolve or
+// onReject function provided to the same call of 'chain' that produced the
+// associated deferred.
+var promiseEvents = new InternalArray;
+
+// Status values: 0 = pending, +1 = resolved, -1 = rejected
+var promiseStatus = NEW_PRIVATE("Promise#status");
+var promiseValue = NEW_PRIVATE("Promise#value");
+var promiseOnResolve = NEW_PRIVATE("Promise#onResolve");
+var promiseOnReject = NEW_PRIVATE("Promise#onReject");
+var promiseRaw = NEW_PRIVATE("Promise#raw");
+
+function IsPromise(x) {
+  return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus);
+}
+
+function Promise(resolver) {
+  if (resolver === promiseRaw) return;
+  var promise = PromiseInit(this);
+  resolver(function(x) { PromiseResolve(promise, x) },
+           function(r) { PromiseReject(promise, r) });
+ // TODO(rossberg): current draft makes exception from this call asynchronous,
+  // but that's probably a mistake.
+}
+
+function PromiseSet(promise, status, value, onResolve, onReject) {
+  SET_PRIVATE(promise, promiseStatus, status);
+  SET_PRIVATE(promise, promiseValue, value);
+  SET_PRIVATE(promise, promiseOnResolve, onResolve);
+  SET_PRIVATE(promise, promiseOnReject, onReject);
+  return promise;
+}
+
+function PromiseInit(promise) {
+ return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray)
+}
+
+function PromiseDone(promise, status, value, promiseQueue) {
+  if (GET_PRIVATE(promise, promiseStatus) !== 0) return;
+  PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
+  PromiseSet(promise, status, value);
+}
+
+function PromiseResolve(promise, x) {
+  PromiseDone(promise, +1, x, promiseOnResolve)
+}
+
+function PromiseReject(promise, r) {
+  PromiseDone(promise, -1, r, promiseOnReject)
+}
+
+
+// Convenience.
+
+function PromiseDeferred() {
+  if (this === $Promise) {
+    // Optimized case, avoid extra closure.
+    var promise = PromiseInit(new Promise(promiseRaw));
+    return {
+      promise: promise,
+      resolve: function(x) { PromiseResolve(promise, x) },
+      reject: function(r) { PromiseReject(promise, r) }
+    };
+  } else {
+    var result = {};
+    result.promise = new this(function(resolve, reject) {
+      result.resolve = resolve;
+      result.reject = reject;
+    })
+    return result;
+  }
+}
+
+function PromiseResolved(x) {
+  if (this === $Promise) {
+    // Optimized case, avoid extra closure.
+    return PromiseSet(new Promise(promiseRaw), +1, x);
+  } else {
+    return new this(function(resolve, reject) { resolve(x) });
+  }
+}
+
+function PromiseRejected(r) {
+  if (this === $Promise) {
+    // Optimized case, avoid extra closure.
+    return PromiseSet(new Promise(promiseRaw), -1, r);
+  } else {
+    return new this(function(resolve, reject) { reject(r) });
+  }
+}
+
+
+// Simple chaining.
+
+function PromiseIdResolveHandler(x) { return x }
+function PromiseIdRejectHandler(r) { throw r }
+
+function PromiseChain(onResolve, onReject) {  // a.k.a.  flatMap
+ onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
+  onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
+  var deferred = %_CallFunction(this.constructor, PromiseDeferred);
+  switch (GET_PRIVATE(this, promiseStatus)) {
+    case UNDEFINED:
+      throw MakeTypeError('not_a_promise', [this]);
+    case 0:  // Pending
+      GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
+      GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
+      break;
+    case +1:  // Resolved
+ PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]);
+      break;
+    case -1:  // Rejected
+ PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]);
+      break;
+  }
+  return deferred.promise;
+}
+
+function PromiseCatch(onReject) {
+  return this.chain(UNDEFINED, onReject);
+}
+
+function PromiseEnqueue(value, tasks) {
+  promiseEvents.push(value, tasks);
+  %SetMicrotaskPending(true);
+}
+
+function PromiseMicrotaskRunner() {
+  var events = promiseEvents;
+  if (events.length > 0) {
+    promiseEvents = new InternalArray;
+    for (var i = 0; i < events.length; i += 2) {
+      var value = events[i];
+      var tasks = events[i + 1];
+      for (var j = 0; j < tasks.length; j += 2) {
+        var handler = tasks[j];
+        var deferred = tasks[j + 1];
+        try {
+          var result = handler(value);
+          if (result === deferred.promise)
+            throw MakeTypeError('promise_cyclic', [result]);
+          else if (IsPromise(result))
+            result.chain(deferred.resolve, deferred.reject);
+          else
+            deferred.resolve(result);
+        } catch(e) {
+          // TODO(rossberg): perhaps log uncaught exceptions below.
+          try { deferred.reject(e) } catch(e) {}
+        }
+      }
+    }
+  }
+}
+RunMicrotasks.runners.push(PromiseMicrotaskRunner);
+
+
+// Multi-unwrapped chaining with thenable coercion.
+
+function PromiseThen(onResolve, onReject) {
+ onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
+  var that = this;
+  var constructor = this.constructor;
+  return this.chain(
+    function(x) {
+      x = PromiseCoerce(constructor, x);
+      return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
+             IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
+    },
+    onReject
+  );
+}
+
+PromiseCoerce.table = new $WeakMap;
+
+function PromiseCoerce(constructor, x) {
+  var then;
+  if (IsPromise(x)) {
+    return x;
+  } else if (!IS_NULL_OR_UNDEFINED(x) && %IsCallable(then = x.then)) {
+    if (PromiseCoerce.table.has(x)) {
+      return PromiseCoerce.table.get(x);
+    } else {
+      var deferred = constructor.deferred();
+      PromiseCoerce.table.set(x, deferred.promise);
+      try {
+        %_CallFunction(x, deferred.resolve, deferred.reject, then);
+      } catch(e) {
+        deferred.reject(e);
+      }
+      return deferred.promise;
+    }
+  } else {
+    return x;
+  }
+}
+
+
+// Combinators.
+
+function PromiseCast(x) {
+  // TODO(rossberg): cannot do better until we support @@create.
+  return IsPromise(x) ? x : this.resolved(x);
+}
+
+function PromiseAll(values) {
+  var deferred = this.deferred();
+  var resolutions = [];
+  var count = values.length;
+  if (count === 0) {
+    deferred.resolve(resolutions);
+  } else {
+    for (var i = 0; i < values.length; ++i) {
+      this.cast(values[i]).chain(
+        function(i, x) {
+          resolutions[i] = x;
+          if (--count === 0) deferred.resolve(resolutions);
+ }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once available
+        function(r) {
+          if (count > 0) { count = 0; deferred.reject(r) }
+        }
+      );
+    }
+  }
+  return deferred.promise;
+}
+
+function PromiseOne(values) {  // a.k.a. race
+  var deferred = this.deferred();
+  var done = false;
+  for (var i = 0; i < values.length; ++i) {
+    this.cast(values[i]).chain(
+      function(x) { if (!done) { done = true; deferred.resolve(x) } },
+      function(r) { if (!done) { done = true; deferred.reject(r) } }
+    );
+  }
+  return deferred.promise;
+}
+
+//-------------------------------------------------------------------
+
+function SetUpPromise() {
+  %CheckIsBootstrapping()
+  global.Promise = $Promise;
+  InstallFunctions($Promise, DONT_ENUM, [
+    "deferred", PromiseDeferred,
+    "resolved", PromiseResolved,
+    "rejected", PromiseRejected,
+    "all", PromiseAll,
+    "one", PromiseOne,
+    "cast", PromiseCast
+  ]);
+  InstallFunctions($Promise.prototype, DONT_ENUM, [
+    "chain", PromiseChain,
+    "then", PromiseThen,
+    "catch", PromiseCatch
+  ]);
+}
+
+SetUpPromise();
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/harmony/promises.js Wed Nov 27 17:21:40 2013 UTC
@@ -0,0 +1,754 @@
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --harmony-promises --harmony-observation --allow-natives-syntax
+
+var asyncAssertsExpected = 0;
+
+function assertAsyncRan() { ++asyncAssertsExpected }
+
+function assertAsync(b, s) {
+  if (b) {
+    print(s, "succeeded")
+  } else {
+    %AbortJS(s + " FAILED!")  // Simply throwing here will have no effect.
+  }
+  --asyncAssertsExpected
+}
+
+function assertAsyncDone(iteration) {
+  var iteration = iteration || 0
+  var dummy = {}
+  Object.observe(dummy,
+    function() {
+      if (asyncAssertsExpected === 0)
+        assertAsync(true, "all")
+      else if (iteration > 10)  // Shouldn't take more.
+        assertAsync(false, "all")
+      else
+        assertAsyncDone(iteration + 1)
+    }
+  )
+  dummy.dummy = dummy
+}
+
+
+(function() {
+  assertThrows(function() { new Promise(5) }, TypeError)
+})();
+
+(function() {
+  assertThrows(function() { new Promise(function() { throw 5 }) }, 5)
+})();
+
+(function() {
+  Promise.resolved(5);
+  Promise.resolved(5).chain(undefined, assertUnreachable).chain(
+    function(x) { assertAsync(x === 5, "resolved/chain-nohandler") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  Promise.rejected(5).chain(assertUnreachable, undefined).chain(
+    assertUnreachable,
+    function(r) { assertAsync(r === 5, "rejected/chain-nohandler") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  Promise.resolved(5).then(undefined, assertUnreachable).chain(
+    function(x) { assertAsync(x === 5, "resolved/then-nohandler") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  Promise.rejected(5).then(assertUnreachable, undefined).chain(
+    assertUnreachable,
+    function(r) { assertAsync(r === 5, "rejected/then-nohandler") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "resolved/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    function(x) { assertAsync(x === 5, "resolved/then") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.rejected(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "rejected/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.rejected(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 5, "rejected/then") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(function(x) { return x }, assertUnreachable).chain(
+    function(x) { assertAsync(x === p1, "resolved/chain/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(function(x) { return x }, assertUnreachable).then(
+    function(x) { assertAsync(x === 5, "resolved/chain/then") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(function(x) { return 6 }, assertUnreachable).chain(
+    function(x) { assertAsync(x === 6, "resolved/chain/chain2") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(function(x) { return 6 }, assertUnreachable).then(
+    function(x) { assertAsync(x === 6, "resolved/chain/then2") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(function(x) { return x + 1 }, assertUnreachable).chain(
+    function(x) { assertAsync(x === 6, "resolved/then/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(function(x) { return x + 1 }, assertUnreachable).then(
+    function(x) { assertAsync(x === 6, "resolved/then/then") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+ p3.then(function(x){ return Promise.resolved(x+1) }, assertUnreachable).chain(
+    function(x) { assertAsync(x === 6, "resolved/then/chain2") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+ p3.then(function(x) { return Promise.resolved(x+1) }, assertUnreachable).then(
+    function(x) { assertAsync(x === 6, "resolved/then/then2") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(function(x) { throw 6 }, assertUnreachable).chain(
+    assertUnreachable,
+    function(x) { assertAsync(x === 6, "resolved/chain-throw/chain") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(function(x) { throw 6 }, assertUnreachable).then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 6, "resolved/chain-throw/then") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(function(x) { throw 6 }, assertUnreachable).chain(
+    assertUnreachable,
+    function(x) { assertAsync(x === 6, "resolved/then-throw/chain") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(function(x) { throw 6 }, assertUnreachable).then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 6, "resolved/then-throw/then") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "resolved/thenable/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    function(x) { assertAsync(x === 5, "resolved/thenable/then") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.rejected(5)
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "rejected/thenable/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.rejected(5)
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 5, "rejected/thenable/then") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "chain/resolve") },
+    assertUnreachable
+  )
+  deferred.resolve(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    function(x) { assertAsync(x === 5, "then/resolve") },
+    assertUnreachable
+  )
+  deferred.resolve(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "chain/reject") },
+    assertUnreachable
+  )
+  deferred.reject(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = Promise.resolved(p1)
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 5, "then/reject") }
+  )
+  deferred.reject(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "chain/resolve/thenable") },
+    assertUnreachable
+  )
+  deferred.resolve(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    function(x) { assertAsync(x === 5, "then/resolve/thenable") },
+    assertUnreachable
+  )
+  deferred.resolve(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.chain(
+    function(x) { assertAsync(x === p2, "chain/reject/thenable") },
+    assertUnreachable
+  )
+  deferred.reject(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var p3 = Promise.resolved(p2)
+  p3.then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 5, "then/reject/thenable") }
+  )
+  deferred.reject(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var deferred = Promise.deferred()
+  var p3 = deferred.promise
+  p3.chain(
+    function(x) { assertAsync(x === p2, "chain/resolve2") },
+    assertUnreachable
+  )
+  deferred.resolve(p2)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var deferred = Promise.deferred()
+  var p3 = deferred.promise
+  p3.then(
+    function(x) { assertAsync(x === 5, "then/resolve2") },
+    assertUnreachable
+  )
+  deferred.resolve(p2)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var deferred = Promise.deferred()
+  var p3 = deferred.promise
+  p3.chain(
+    assertUnreachable,
+    function(x) { assertAsync(x === 5, "chain/reject2") }
+  )
+  deferred.reject(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = Promise.resolved(p1)
+  var deferred = Promise.deferred()
+  var p3 = deferred.promise
+  p3.then(
+    assertUnreachable,
+    function(x) { assertAsync(x === 5, "then/reject2") }
+  )
+  deferred.reject(5)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var deferred = Promise.deferred()
+  var p3 = deferred.promise
+  p3.chain(
+    function(x) { assertAsync(x === p2, "chain/resolve/thenable2") },
+    assertUnreachable
+  )
+  deferred.resolve(p2)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(5)
+  var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
+  var deferred = Promise.deferred()
+  var p3 = deferred.promise
+  p3.then(
+    function(x) { assertAsync(x === 5, "then/resolve/thenable2") },
+    assertUnreachable
+  )
+  deferred.resolve(p2)
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(0)
+  var p2 = p1.chain(function(x) { return p2 }, assertUnreachable)
+  p2.chain(
+    assertUnreachable,
+    function(r) { assertAsync(r instanceof TypeError, "cyclic/chain") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(0)
+  var p2 = p1.then(function(x) { return p2 }, assertUnreachable)
+  p2.chain(
+    assertUnreachable,
+    function(r) { assertAsync(r instanceof TypeError, "cyclic/then") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p = deferred.promise
+  deferred.resolve(p)
+  p.chain(
+    function(x) { assertAsync(x === p, "cyclic/deferred/chain") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p = deferred.promise
+  deferred.resolve(p)
+  p.then(
+    assertUnreachable,
+ function(r) { assertAsync(r instanceof TypeError, "cyclic/deferred/then") }
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  Promise.all([]).chain(
+    function(x) { assertAsync(x.length === 0, "all/resolve/empty") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred1 = Promise.deferred()
+  var p1 = deferred1.promise
+  var deferred2 = Promise.deferred()
+  var p2 = deferred2.promise
+  var deferred3 = Promise.deferred()
+  var p3 = deferred3.promise
+  Promise.all([p1, p2, p3]).chain(
+    function(x) {
+      assertAsync(x.length === 3, "all/resolve")
+      assertAsync(x[0] === 1, "all/resolve/0")
+      assertAsync(x[1] === 2, "all/resolve/1")
+      assertAsync(x[2] === 3, "all/resolve/2")
+    },
+    assertUnreachable
+  )
+  deferred1.resolve(1)
+  deferred3.resolve(3)
+  deferred2.resolve(2)
+  assertAsyncRan()
+  assertAsyncRan()
+  assertAsyncRan()
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = Promise.resolved(2)
+  var p3 = Promise.deferred().promise
+  Promise.all([p1, p2, p3]).chain(
+    assertUnreachable,
+    assertUnreachable
+  )
+  deferred.resolve(1)
+})();
+
+(function() {
+  var deferred1 = Promise.deferred()
+  var p1 = deferred1.promise
+  var deferred2 = Promise.deferred()
+  var p2 = deferred2.promise
+  var deferred3 = Promise.deferred()
+  var p3 = deferred3.promise
+  Promise.all([p1, p2, p3]).chain(
+    assertUnreachable,
+    function(x) { assertAsync(x === 2, "all/reject") }
+  )
+  deferred1.resolve(1)
+  deferred3.resolve(3)
+  deferred2.reject(2)
+  assertAsyncRan()
+})();
+
+(function() {
+  Promise.one([]).chain(
+    assertUnreachable,
+    assertUnreachable
+  )
+})();
+
+(function() {
+  var p1 = Promise.resolved(1)
+  var p2 = Promise.resolved(2)
+  var p3 = Promise.resolved(3)
+  Promise.one([p1, p2, p3]).chain(
+    function(x) { assertAsync(x === 1, "resolved/all") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var p1 = Promise.resolved(1)
+  var p2 = Promise.resolved(2)
+  var p3 = Promise.resolved(3)
+  Promise.one([0, p1, p2, p3]).chain(
+    function(x) { assertAsync(x === 0, "resolved-const/all") },
+    assertUnreachable
+  )
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred1 = Promise.deferred()
+  var p1 = deferred1.promise
+  var deferred2 = Promise.deferred()
+  var p2 = deferred2.promise
+  var deferred3 = Promise.deferred()
+  var p3 = deferred3.promise
+  Promise.one([p1, p2, p3]).chain(
+    function(x) { assertAsync(x === 3, "one/resolve") },
+    assertUnreachable
+  )
+  deferred3.resolve(3)
+  deferred1.resolve(1)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred = Promise.deferred()
+  var p1 = deferred.promise
+  var p2 = Promise.resolved(2)
+  var p3 = Promise.deferred().promise
+  Promise.one([p1, p2, p3]).chain(
+    function(x) { assertAsync(x === 2, "resolved/one") },
+    assertUnreachable
+  )
+  deferred.resolve(1)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred1 = Promise.deferred()
+  var p1 = deferred1.promise
+  var deferred2 = Promise.deferred()
+  var p2 = deferred2.promise
+  var deferred3 = Promise.deferred()
+  var p3 = deferred3.promise
+  Promise.one([p1, p2, p3]).chain(
+    function(x) { assertAsync(x === 3, "one/resolve/reject") },
+    assertUnreachable
+  )
+  deferred3.resolve(3)
+  deferred1.reject(1)
+  assertAsyncRan()
+})();
+
+(function() {
+  var deferred1 = Promise.deferred()
+  var p1 = deferred1.promise
+  var deferred2 = Promise.deferred()
+  var p2 = deferred2.promise
+  var deferred3 = Promise.deferred()
+  var p3 = deferred3.promise
+  Promise.one([p1, p2, p3]).chain(
+    assertUnreachable,
+    function(x) { assertAsync(x === 3, "one/reject/resolve") }
+  )
+  deferred3.reject(3)
+  deferred1.resolve(1)
+  assertAsyncRan()
+})();
+
+(function() {
+  var log
+  function MyPromise(resolver) {
+    log += "n"
+    Promise.call(this,
+      function(resolve, reject) {
+        resolver(
+          function(x) { log += "x" + x; resolve(x) },
+          function(r) { log += "r" + r; reject(r) }
+        )
+      }
+    )
+  }
+
+  MyPromise.__proto__ = Promise
+  MyPromise.deferred = function() {
+    log += "d"
+    return this.__proto__.deferred.call(this)
+  }
+
+  MyPromise.prototype.__proto__ = Promise.prototype
+  MyPromise.prototype.chain = function(resolve, reject) {
+    log += "w"
+    return this.__proto__.__proto__.chain.call(this, resolve, reject)
+  }
+
+  log = ""
+  var p1 = new MyPromise(function(resolve, reject) { resolve(1) })
+  var p2 = new MyPromise(function(resolve, reject) { reject(2) })
+  var d3 = MyPromise.deferred()
+  assertTrue(d3.promise instanceof MyPromise, "subclass/instance3")
+  assertTrue(log === "nx1nr2dn", "subclass/create")
+
+  log = ""
+  var p4 = MyPromise.resolved(4)
+  var p5 = MyPromise.rejected(5)
+  assertTrue(p4 instanceof MyPromise, "subclass/instance4")
+  assertTrue(p5 instanceof MyPromise, "subclass/instance5")
+  d3.resolve(3)
+  assertTrue(log === "nx4nr5x3", "subclass/resolve")
+
+  log = ""
+  var d6 = MyPromise.deferred()
+ d6.promise.chain(function(x) { return new Promise(x) }).chain(function() {})
+  d6.resolve(6)
+  assertTrue(log === "dnwnwnx6", "subclass/chain")
+
+  log = ""
+ Promise.all([11, Promise.resolved(12), 13, MyPromise.resolved(14), 15, 16])
+  assertTrue(log === "nx14wn", "subclass/all/arg")
+
+  log = ""
+ MyPromise.all([21, Promise.resolved(22), 23, MyPromise.resolved(24), 25, 26]) + assertTrue(log === "nx24dnnx21wnnx23wnwnnx25wnnx26wn", "subclass/all/self")
+})();
+
+
+assertAsyncDone()
=======================================
--- /branches/bleeding_edge/src/api.cc  Tue Nov 26 09:45:17 2013 UTC
+++ /branches/bleeding_edge/src/api.cc  Wed Nov 27 17:21:40 2013 UTC
@@ -4054,8 +4054,7 @@
   ENTER_V8(isolate);
   i::HandleScope scope(isolate);
   i::Handle<i::JSObject> obj = Utils::OpenHandle(this);
-  if (obj->IsJSFunction()) return true;
-  return i::Execution::GetFunctionDelegate(isolate, obj)->IsJSFunction();
+  return obj->IsCallable();
 }


=======================================
--- /branches/bleeding_edge/src/bootstrapper.cc Wed Nov 27 09:22:04 2013 UTC
+++ /branches/bleeding_edge/src/bootstrapper.cc Wed Nov 27 17:21:40 2013 UTC
@@ -1579,6 +1579,7 @@


 void Genesis::InstallExperimentalNativeFunctions() {
+  INSTALL_NATIVE(JSFunction, "RunMicrotasks", run_microtasks);
   if (FLAG_harmony_proxies) {
     INSTALL_NATIVE(JSFunction, "DerivedHasTrap", derived_has_trap);
     INSTALL_NATIVE(JSFunction, "DerivedGetTrap", derived_get_trap);
@@ -1592,8 +1593,6 @@
                    observers_begin_perform_splice);
     INSTALL_NATIVE(JSFunction, "EndPerformSplice",
                    observers_end_perform_splice);
-    INSTALL_NATIVE(JSFunction, "DeliverChangeRecords",
-                   observers_deliver_changes);
   }
 }

@@ -2022,57 +2021,30 @@

   return true;
 }
+
+
+#define INSTALL_EXPERIMENTAL_NATIVE(i, flag, file)                \
+  if (FLAG_harmony_##flag &&                                      \
+      strcmp(ExperimentalNatives::GetScriptName(i).start(),       \
+          "native " file) == 0) {                                 \
+    if (!CompileExperimentalBuiltin(isolate(), i)) return false;  \
+  }


 bool Genesis::InstallExperimentalNatives() {
   for (int i = ExperimentalNatives::GetDebuggerCount();
        i < ExperimentalNatives::GetBuiltinsCount();
        i++) {
-    if (FLAG_harmony_symbols &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native symbol.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_proxies &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native proxy.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_collections &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native collection.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_observation &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native object-observe.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_generators &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native generator.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_iteration &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native array-iterator.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_strings &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native harmony-string.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_arrays &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native harmony-array.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
-    if (FLAG_harmony_maths &&
-        strcmp(ExperimentalNatives::GetScriptName(i).start(),
-               "native harmony-math.js") == 0) {
-      if (!CompileExperimentalBuiltin(isolate(), i)) return false;
-    }
+    INSTALL_EXPERIMENTAL_NATIVE(i, symbols, "symbol.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, proxies, "proxy.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, collections, "collection.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, observation, "object-observe.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, promises, "promise.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, generators, "generator.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, iteration, "array-iterator.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, strings, "harmony-string.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, arrays, "harmony-array.js")
+    INSTALL_EXPERIMENTAL_NATIVE(i, maths, "harmony-math.js")
   }

   InstallExperimentalNativeFunctions();
=======================================
--- /branches/bleeding_edge/src/contexts.h      Fri Nov 22 11:35:39 2013 UTC
+++ /branches/bleeding_edge/src/contexts.h      Wed Nov 27 17:21:40 2013 UTC
@@ -166,6 +166,7 @@
V(ALLOW_CODE_GEN_FROM_STRINGS_INDEX, Object, allow_code_gen_from_strings) \
   V(ERROR_MESSAGE_FOR_CODE_GEN_FROM_STRINGS_INDEX, Object, \
     error_message_for_code_gen_from_strings) \
+  V(RUN_MICROTASKS_INDEX, JSFunction, run_microtasks) \
   V(TO_COMPLETE_PROPERTY_DESCRIPTOR_INDEX, JSFunction, \
     to_complete_property_descriptor) \
   V(DERIVED_HAS_TRAP_INDEX, JSFunction, derived_has_trap) \
@@ -178,7 +179,6 @@
     observers_begin_perform_splice) \
   V(OBSERVERS_END_SPLICE_INDEX, JSFunction, \
     observers_end_perform_splice) \
- V(OBSERVERS_DELIVER_CHANGES_INDEX, JSFunction, observers_deliver_changes) \
   V(GENERATOR_FUNCTION_MAP_INDEX, Map, generator_function_map) \
   V(STRICT_MODE_GENERATOR_FUNCTION_MAP_INDEX, Map, \
     strict_mode_generator_function_map) \
@@ -317,6 +317,7 @@
     EMBEDDER_DATA_INDEX,
     ALLOW_CODE_GEN_FROM_STRINGS_INDEX,
     ERROR_MESSAGE_FOR_CODE_GEN_FROM_STRINGS_INDEX,
+    RUN_MICROTASKS_INDEX,
     TO_COMPLETE_PROPERTY_DESCRIPTOR_INDEX,
     DERIVED_HAS_TRAP_INDEX,
     DERIVED_GET_TRAP_INDEX,
@@ -326,7 +327,6 @@
     OBSERVERS_ENQUEUE_SPLICE_INDEX,
     OBSERVERS_BEGIN_SPLICE_INDEX,
     OBSERVERS_END_SPLICE_INDEX,
-    OBSERVERS_DELIVER_CHANGES_INDEX,
     GENERATOR_FUNCTION_MAP_INDEX,
     STRICT_MODE_GENERATOR_FUNCTION_MAP_INDEX,
     GENERATOR_OBJECT_PROTOTYPE_MAP_INDEX,
=======================================
--- /branches/bleeding_edge/src/execution.cc    Thu Nov 21 13:47:37 2013 UTC
+++ /branches/bleeding_edge/src/execution.cc    Wed Nov 27 17:21:40 2013 UTC
@@ -352,6 +352,20 @@

   return isolate->factory()->undefined_value();
 }
+
+
+void Execution::RunMicrotasks(Isolate* isolate) {
+  ASSERT(isolate->microtask_pending());
+  bool threw = false;
+  Execution::Call(
+      isolate,
+      isolate->run_microtasks(),
+      isolate->factory()->undefined_value(),
+      0,
+      NULL,
+      &threw);
+  ASSERT(!threw);
+}


 bool StackGuard::IsStackOverflow() {
=======================================
--- /branches/bleeding_edge/src/execution.h     Thu Sep 12 11:30:56 2013 UTC
+++ /branches/bleeding_edge/src/execution.h     Wed Nov 27 17:21:40 2013 UTC
@@ -171,6 +171,8 @@
   static Handle<Object> TryGetConstructorDelegate(Isolate* isolate,
                                                   Handle<Object> object,
bool* has_pending_exception);
+
+  static void RunMicrotasks(Isolate* isolate);
 };


=======================================
--- /branches/bleeding_edge/src/flag-definitions.h Wed Nov 27 09:22:04 2013 UTC +++ /branches/bleeding_edge/src/flag-definitions.h Wed Nov 27 17:21:40 2013 UTC
@@ -171,6 +171,7 @@
             "enable harmony modules (implies block scoping)")
 DEFINE_bool(harmony_symbols, false,
             "enable harmony symbols (a.k.a. private names)")
+DEFINE_bool(harmony_promises, false, "enable harmony promises")
 DEFINE_bool(harmony_proxies, false, "enable harmony proxies")
 DEFINE_bool(harmony_collections, false,
             "enable harmony collections (sets, maps, and weak maps)")
@@ -187,6 +188,7 @@
 DEFINE_implication(harmony, harmony_scoping)
 DEFINE_implication(harmony, harmony_modules)
 DEFINE_implication(harmony, harmony_symbols)
+DEFINE_implication(harmony, harmony_promises)
 DEFINE_implication(harmony, harmony_proxies)
 DEFINE_implication(harmony, harmony_collections)
 DEFINE_implication(harmony, harmony_observation)
@@ -196,6 +198,7 @@
 DEFINE_implication(harmony, harmony_strings)
 DEFINE_implication(harmony, harmony_arrays)
 DEFINE_implication(harmony, harmony_maths)
+DEFINE_implication(harmony_promises, harmony_collections)
 DEFINE_implication(harmony_modules, harmony_scoping)
 DEFINE_implication(harmony_observation, harmony_collections)

=======================================
--- /branches/bleeding_edge/src/isolate.h       Thu Nov 21 13:47:37 2013 UTC
+++ /branches/bleeding_edge/src/isolate.h       Wed Nov 27 17:21:40 2013 UTC
@@ -359,7 +359,7 @@
/* AstNode state. */ \ V(int, ast_node_id, 0) \ V(unsigned, ast_node_count, 0) \ - V(bool, observer_delivery_pending, false) \ + V(bool, microtask_pending, false) \ V(HStatistics*, hstatistics, NULL) \ V(HTracer*, htracer, NULL) \ V(CodeTracer*, code_tracer, NULL) \
=======================================
--- /branches/bleeding_edge/src/messages.js     Wed Nov 13 10:34:06 2013 UTC
+++ /branches/bleeding_edge/src/messages.js     Wed Nov 27 17:21:40 2013 UTC
@@ -109,6 +109,8 @@
   invalid_argument:              ["invalid_argument"],
data_view_not_array_buffer: ["First argument to DataView constructor must be an ArrayBuffer"],
   constructor_not_function:      ["Constructor ", "%0", " requires 'new'"],
+  not_a_promise:                 ["%0", "is not a promise"],
+ promise_cyclic: ["Chaining cycle detected for promise", "%0"],
   // RangeError
   invalid_array_length:          ["Invalid array length"],
   invalid_array_buffer_length:   ["Invalid array buffer length"],
=======================================
--- /branches/bleeding_edge/src/object-observe.js Wed Nov 6 12:14:24 2013 UTC +++ /branches/bleeding_edge/src/object-observe.js Wed Nov 27 17:21:40 2013 UTC
@@ -384,7 +384,7 @@
     observationState.pendingObservers = { __proto__: null };
   observationState.pendingObservers[callbackInfo.priority] = callback;
   callbackInfo.push(changeRecord);
-  %SetObserverDeliveryPending();
+  %SetMicrotaskPending(true);
 }

function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) {
@@ -551,7 +551,7 @@

   try {
     %_CallFunction(UNDEFINED, delivered, callback);
-  } catch (ex) {}
+  } catch (ex) {}  // TODO(rossberg): perhaps log uncaught exceptions.
   return true;
 }

@@ -562,15 +562,16 @@
   while (CallbackDeliverPending(callback)) {}
 }

-function DeliverChangeRecords() {
-  while (observationState.pendingObservers) {
-    var pendingObservers = observationState.pendingObservers;
+function ObserveMicrotaskRunner() {
+  var pendingObservers = observationState.pendingObservers;
+  if (pendingObservers) {
     observationState.pendingObservers = null;
     for (var i in pendingObservers) {
       CallbackDeliverPending(pendingObservers[i]);
     }
   }
 }
+RunMicrotasks.runners.push(ObserveMicrotaskRunner);

 function SetupObjectObserve() {
   %CheckIsBootstrapping();
=======================================
--- /branches/bleeding_edge/src/objects.cc      Wed Nov 27 14:03:40 2013 UTC
+++ /branches/bleeding_edge/src/objects.cc      Wed Nov 27 17:21:40 2013 UTC
@@ -118,6 +118,17 @@
if (IsHeapNumber()) return HeapNumber::cast(this)->HeapNumberBooleanValue();
   return true;
 }
+
+
+bool Object::IsCallable() {
+  Object* fun = this;
+  while (fun->IsJSFunctionProxy()) {
+    fun = JSFunctionProxy::cast(fun)->call_trap();
+  }
+  return fun->IsJSFunction() ||
+         (fun->IsHeapObject() &&
+          HeapObject::cast(fun)->map()->has_instance_call_handler());
+}


 void Object::Lookup(Name* name, LookupResult* result) {
@@ -2216,21 +2227,6 @@
                   &threw);
   ASSERT(!threw);
 }
-
-
-void JSObject::DeliverChangeRecords(Isolate* isolate) {
-  ASSERT(isolate->observer_delivery_pending());
-  bool threw = false;
-  Execution::Call(
-      isolate,
-      isolate->observers_deliver_changes(),
-      isolate->factory()->undefined_value(),
-      0,
-      NULL,
-      &threw);
-  ASSERT(!threw);
-  isolate->set_observer_delivery_pending(false);
-}


 Handle<Object> JSObject::SetPropertyPostInterceptor(
=======================================
--- /branches/bleeding_edge/src/objects.h       Wed Nov 27 14:03:40 2013 UTC
+++ /branches/bleeding_edge/src/objects.h       Wed Nov 27 17:21:40 2013 UTC
@@ -1360,6 +1360,7 @@

   INLINE(bool IsSpecObject());
   INLINE(bool IsSpecFunction());
+  bool IsCallable();

   // Oddball testing.
   INLINE(bool IsUndefined());
@@ -2639,9 +2640,6 @@
                                   Handle<Name> name,
                                   Handle<Object> old_value);

-  // Deliver change records to observers. May cause GC.
-  static void DeliverChangeRecords(Isolate* isolate);
-
  private:
   friend class DictionaryElementsAccessor;
   friend class JSReceiver;
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Tue Nov 26 14:21:46 2013 UTC
+++ /branches/bleeding_edge/src/runtime.cc      Wed Nov 27 17:21:40 2013 UTC
@@ -2661,6 +2661,14 @@

   return *holder;
 }
+
+
+RUNTIME_FUNCTION(MaybeObject*, Runtime_IsCallable) {
+  SealHandleScope shs(isolate);
+  ASSERT(args.length() == 1);
+  CONVERT_ARG_CHECKED(Object, obj, 0);
+  return isolate->heap()->ToBoolean(obj->IsCallable());
+}


 RUNTIME_FUNCTION(MaybeObject*, Runtime_IsClassicModeFunction) {
@@ -14321,6 +14329,18 @@
   UNREACHABLE();
   return NULL;
 }
+
+
+RUNTIME_FUNCTION(MaybeObject*, Runtime_AbortJS) {
+  HandleScope scope(isolate);
+  ASSERT(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(String, message, 0);
+  OS::PrintError("abort: %s\n", *message->ToCString());
+  isolate->PrintStack(stderr);
+  OS::Abort();
+  UNREACHABLE();
+  return NULL;
+}


 RUNTIME_FUNCTION(MaybeObject*, Runtime_FlattenString) {
@@ -14615,11 +14635,13 @@
 }


-RUNTIME_FUNCTION(MaybeObject*, Runtime_SetObserverDeliveryPending) {
+RUNTIME_FUNCTION(MaybeObject*, Runtime_SetMicrotaskPending) {
   SealHandleScope shs(isolate);
-  ASSERT(args.length() == 0);
-  isolate->set_observer_delivery_pending(true);
-  return isolate->heap()->undefined_value();
+  ASSERT(args.length() == 1);
+  CONVERT_BOOLEAN_ARG_CHECKED(new_state, 0);
+  bool old_state = isolate->microtask_pending();
+  isolate->set_microtask_pending(new_state);
+  return isolate->heap()->ToBoolean(old_state);
 }


=======================================
--- /branches/bleeding_edge/src/runtime.h       Tue Nov 26 12:29:47 2013 UTC
+++ /branches/bleeding_edge/src/runtime.h       Wed Nov 27 17:21:40 2013 UTC
@@ -64,6 +64,7 @@
   F(ToFastProperties, 1, 1) \
   F(FinishArrayPrototypeSetup, 1, 1) \
   F(SpecialArrayFunctions, 1, 1) \
+  F(IsCallable, 1, 1) \
   F(IsClassicModeFunction, 1, 1) \
   F(GetDefaultReceiver, 1, 1) \
   \
@@ -352,10 +353,12 @@
   F(WeakCollectionDelete, 2, 1) \
   F(WeakCollectionSet, 3, 1) \
   \
+  /* Harmony events */ \
+  F(SetMicrotaskPending, 1, 1) \
+  \
   /* Harmony observe */ \
   F(IsObserved, 1, 1) \
   F(SetIsObserved, 1, 1) \
-  F(SetObserverDeliveryPending, 0, 1) \
   F(GetObservationState, 0, 1) \
   F(ObservationWeakMapCreate, 0, 1) \
   F(UnwrapGlobalProxy, 1, 1) \
@@ -439,6 +442,7 @@
   F(TraceEnter, 0, 1) \
   F(TraceExit, 1, 1) \
   F(Abort, 2, 1) \
+  F(AbortJS, 1, 1) \
   /* Logging */ \
   F(Log, 2, 1) \
   /* ES5 */ \
=======================================
--- /branches/bleeding_edge/src/v8.cc   Fri Nov 22 11:35:39 2013 UTC
+++ /branches/bleeding_edge/src/v8.cc   Wed Nov 27 17:21:40 2013 UTC
@@ -142,17 +142,15 @@

 void V8::FireCallCompletedCallback(Isolate* isolate) {
   bool has_call_completed_callbacks = call_completed_callbacks_ != NULL;
-  bool observer_delivery_pending =
-      FLAG_harmony_observation && isolate->observer_delivery_pending();
-  if (!has_call_completed_callbacks && !observer_delivery_pending) return;
+  bool microtask_pending = isolate->microtask_pending();
+  if (!has_call_completed_callbacks && !microtask_pending) return;
+
   HandleScopeImplementer* handle_scope_implementer =
       isolate->handle_scope_implementer();
   if (!handle_scope_implementer->CallDepthIsZero()) return;
   // Fire callbacks.  Increase call depth to prevent recursive callbacks.
   handle_scope_implementer->IncrementCallDepth();
-  if (observer_delivery_pending) {
-    JSObject::DeliverChangeRecords(isolate);
-  }
+  if (microtask_pending) Execution::RunMicrotasks(isolate);
   if (has_call_completed_callbacks) {
     for (int i = 0; i < call_completed_callbacks_->length(); i++) {
       call_completed_callbacks_->at(i)();
=======================================
--- /branches/bleeding_edge/src/v8natives.js    Tue Nov  5 12:25:32 2013 UTC
+++ /branches/bleeding_edge/src/v8natives.js    Wed Nov 27 17:21:40 2013 UTC
@@ -1837,3 +1837,18 @@
 }

 SetUpFunction();
+
+
+//----------------------------------------------------------------------------
+
+// TODO(rossberg): very simple abstraction for generic microtask queue.
+// Eventually, we should move to a real event queue that allows to maintain
+// relative ordering of different kinds of tasks.
+
+RunMicrotasks.runners = new InternalArray;
+
+function RunMicrotasks() {
+  while (%SetMicrotaskPending(false)) {
+    for (var i in RunMicrotasks.runners) RunMicrotasks.runners[i]();
+  }
+}
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/iteration-semantics.js Tue Jun 11 14:45:17 2013 UTC +++ /branches/bleeding_edge/test/mjsunit/harmony/iteration-semantics.js Wed Nov 27 17:21:40 2013 UTC
@@ -25,7 +25,8 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-// Flags: --harmony --harmony-generators
+// Flags: --harmony-iteration
+// Flags: --harmony-generators --harmony-scoping --harmony-proxies

 // Test for-of semantics.

=======================================
--- /branches/bleeding_edge/tools/gyp/v8.gyp    Wed Nov 27 09:22:04 2013 UTC
+++ /branches/bleeding_edge/tools/gyp/v8.gyp    Wed Nov 27 17:21:40 2013 UTC
@@ -978,6 +978,7 @@
           '../../src/proxy.js',
           '../../src/collection.js',
           '../../src/object-observe.js',
+          '../../src/promise.js',
           '../../src/generator.js',
           '../../src/array-iterator.js',
           '../../src/harmony-string.js',

--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
--- You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to