Bug 1568903 - Part 9: Implement the Promise.any proposal. r=jorendorff
☠☠ backed out by cfe990fab350 ☠ ☠
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 12 Nov 2019 11:14:48 +0000
changeset 502145 f4d9fda6d7f2504b1e8bde4f50603a7013f0917f
parent 502144 379d0f2de211fb18470dfe40f41199dcc8b44cb4
child 502146 76ad398222a6809bef3116d77b580ef3e9339cce
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1568903
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1568903 - Part 9: Implement the Promise.any proposal. r=jorendorff As with AggregateError, `Promise.any` is only enabled in Nightly. Now that everything is in place, the actual `Promise.any` implementation is relatively straight forward. The only tricky part is probably just the `ThrowAggregateError` function, when the async stack is created to give a better stack trace. Differential Revision: https://phabricator.services.mozilla.com/D51659
js/src/builtin/Promise.cpp
js/src/builtin/Utilities.js
js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js
js/src/js.msg
js/src/jsexn.cpp
js/src/jsexn.h
js/src/tests/non262/Promise/any-stack.js
js/src/tests/non262/Promise/any.js
js/src/vm/CommonPropertyNames.h
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -15,26 +15,29 @@
 #include "jsexn.h"
 #include "jsfriendapi.h"
 
 #include "gc/Heap.h"
 #include "js/Debug.h"
 #include "js/ForOfIterator.h"  // JS::ForOfIterator
 #include "js/PropertySpec.h"
 #include "util/Poison.h"
+#include "vm/ArrayObject.h"
 #include "vm/AsyncFunction.h"
 #include "vm/AsyncIteration.h"
+#include "vm/ErrorObject.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Iteration.h"
 #include "vm/JSContext.h"
 #include "vm/JSObject.h"
 #include "vm/SelfHosting.h"
 
 #include "debugger/DebugAPI-inl.h"
 #include "vm/Compartment-inl.h"
+#include "vm/ErrorObject-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
 static double MillisecondsSinceStartup(
     const mozilla::Maybe<mozilla::TimeStamp>& maybeNow) {
   auto now = maybeNow.isSome() ? maybeNow.ref() : mozilla::TimeStamp::Now();
@@ -2384,47 +2387,55 @@ class MOZ_STACK_CLASS PromiseForOfIterat
 static MOZ_MUST_USE bool PerformPromiseAll(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done);
 
 static MOZ_MUST_USE bool PerformPromiseAllSettled(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done);
 
+static MOZ_MUST_USE bool PerformPromiseAny(
+    JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+    Handle<PromiseCapability> resultCapability, bool* done);
+
 static MOZ_MUST_USE bool PerformPromiseRace(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done);
 
-enum class CombinatorKind { All, AllSettled, Race };
-
-// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+enum class CombinatorKind { All, AllSettled, Any, Race };
+
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
 //
 // Unified implementation of
 // 25.6.4.1 Promise.all ( iterable )
-// 25.6.4.3 Promise.race ( iterable )
+// 25.6.4.2 Promise.allSettled ( iterable )
+// 25.6.4.4 Promise.race ( iterable )
 //
-// Promise.allSettled (Stage 4 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
 //
-// Promise.allSettled ( iterable )
+// Promise.any ( iterable )
 static MOZ_MUST_USE bool CommonPromiseCombinator(JSContext* cx, CallArgs& args,
                                                  CombinatorKind kind) {
   HandleValue iterable = args.get(0);
 
   // Step 2 (moved from NewPromiseCapability, step 1).
   HandleValue CVal = args.thisv();
   if (!CVal.isObject()) {
     const char* message;
     switch (kind) {
       case CombinatorKind::All:
         message = "Receiver of Promise.all call";
         break;
       case CombinatorKind::AllSettled:
         message = "Receiver of Promise.allSettled call";
         break;
+      case CombinatorKind::Any:
+        message = "Receiver of Promise.any call";
+        break;
       case CombinatorKind::Race:
         message = "Receiver of Promise.race call";
         break;
     }
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_OBJECT_REQUIRED, message);
     return false;
   }
@@ -2448,16 +2459,19 @@ static MOZ_MUST_USE bool CommonPromiseCo
     const char* message;
     switch (kind) {
       case CombinatorKind::All:
         message = "Argument of Promise.all";
         break;
       case CombinatorKind::AllSettled:
         message = "Argument of Promise.allSettled";
         break;
+      case CombinatorKind::Any:
+        message = "Argument of Promise.any";
+        break;
       case CombinatorKind::Race:
         message = "Argument of Promise.race";
         break;
     }
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
                               message);
     return AbruptRejectPromise(cx, args, promiseCapability);
   }
@@ -2466,16 +2480,19 @@ static MOZ_MUST_USE bool CommonPromiseCo
   bool done, result;
   switch (kind) {
     case CombinatorKind::All:
       result = PerformPromiseAll(cx, iter, C, promiseCapability, &done);
       break;
     case CombinatorKind::AllSettled:
       result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done);
       break;
+    case CombinatorKind::Any:
+      result = PerformPromiseAny(cx, iter, C, promiseCapability, &done);
+      break;
     case CombinatorKind::Race:
       result = PerformPromiseRace(cx, iter, C, promiseCapability, &done);
       break;
   }
 
   // Step 6.
   if (!result) {
     // Step 6.a.
@@ -2701,23 +2718,24 @@ static MOZ_MUST_USE bool RunResolutionFu
 }
 
 static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(
     JSContext* cx, HandleValue thisVal, HandleValue argVal,
     ResolutionMode mode);
 
 static bool IsPromiseSpecies(JSContext* cx, JSFunction* species);
 
-// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
 // 25.6.4.1.1 Runtime Semantics: PerformPromiseAll, steps 5-6 and step 8.
-// 25.6.4.3.1 Runtime Semantics: PerformPromiseRace, steps 3-5.
+// 25.6.4.2.1 Runtime Semantics: PerformPromiseAllSettled, steps 5-6 and step 8.
+// 25.6.4.4.1 Runtime Semantics: PerformPromiseRace, steps 3-5.
 //
-// Promise.allSettled (Stage 4 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
-// Runtime Semantics: PerformPromiseAllSettled, steps 5-6 and step 8.
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+// Runtime Semantics: PerformPromiseAny, steps 6-8.
 template <typename T>
 static MOZ_MUST_USE bool CommonPerformPromiseCombinator(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     HandleObject resultPromise, bool* done, bool resolveReturnsUndefined,
     T getResolveAndReject) {
   RootedObject promiseCtor(
       cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
   if (!promiseCtor) {
@@ -3311,36 +3329,36 @@ static MOZ_MUST_USE bool PerformPromiseR
   // Steps 3-5.
   return CommonPerformPromiseCombinator(
       cx, iterator, C, resultCapability.promise(), done, isDefaultResolveFn,
       getResolveAndReject);
 }
 
 enum class PromiseAllSettledElementFunctionKind { Resolve, Reject };
 
-// Promise.allSettled (Stage 4 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.2 Promise.allSettled ( iterable )
 //
 // Promise.allSettled Resolve Element Functions
 // Promise.allSettled Reject Element Functions
 template <PromiseAllSettledElementFunctionKind Kind>
 static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
                                              Value* vp);
 
-// Promise.allSettled (Stage 4 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.2 Promise.allSettled ( iterable )
 //
 // Promise.allSettled ( iterable )
 static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return CommonPromiseCombinator(cx, args, CombinatorKind::AllSettled);
 }
 
-// Promise.allSettled (Stage 4 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.2 Promise.allSettled ( iterable )
 //
 // PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability )
 static MOZ_MUST_USE bool PerformPromiseAllSettled(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done) {
   *done = false;
 
   // Step 1.
@@ -3516,16 +3534,234 @@ static bool PromiseAllSettledElementFunc
     }
   }
 
   // Step 15.
   args.rval().setUndefined();
   return true;
 }
 
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// Promise.any ( iterable )
+static bool Promise_static_any(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  return CommonPromiseCombinator(cx, args, CombinatorKind::Any);
+}
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// Promise.any Reject Element Functions
+static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
+                                            Value* vp);
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// ThrowAggregateError ( errors )
+static void ThrowAggregateError(JSContext* cx,
+                                Handle<PromiseCombinatorElements> errors,
+                                HandleObject promise);
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// PerformPromiseAny ( iteratorRecord, constructor, resultCapability )
+static MOZ_MUST_USE bool PerformPromiseAny(
+    JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+    Handle<PromiseCapability> resultCapability, bool* done) {
+  *done = false;
+
+  // Step 1.
+  MOZ_ASSERT(C->isConstructor());
+
+  // Step 2 (omitted).
+
+  // Step 3.
+  Rooted<PromiseCombinatorElements> errors(cx);
+  if (!NewPromiseCombinatorElements(cx, resultCapability, &errors)) {
+    return false;
+  }
+
+  // Step 4.
+  // Create our data holder that holds all the things shared across every step
+  // of the iterator. In particular, this holds the remainingElementsCount (as
+  // an integer reserved slot), the array of errors, and the reject function
+  // from our PromiseCapability.
+  Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
+  dataHolder = PromiseCombinatorDataHolder::New(
+      cx, resultCapability.promise(), errors, resultCapability.reject());
+  if (!dataHolder) {
+    return false;
+  }
+
+  // Step 5.
+  uint32_t index = 0;
+
+  auto getResolveAndReject = [cx, &resultCapability, &errors, &dataHolder,
+                              &index](MutableHandleValue resolveFunVal,
+                                      MutableHandleValue rejectFunVal) {
+    // Step 8.h.
+    if (!errors.pushUndefined(cx)) {
+      return false;
+    }
+
+    // Steps 8.j-p.
+    JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
+        cx, PromiseAnyRejectElementFunction, dataHolder, index);
+    if (!rejectFunc) {
+      return false;
+    }
+
+    // Step 8.q.
+    dataHolder->increaseRemainingCount();
+
+    // Step 8.s.
+    index++;
+    MOZ_ASSERT(index > 0);
+
+    resolveFunVal.setObject(*resultCapability.resolve());
+    rejectFunVal.setObject(*rejectFunc);
+    return true;
+  };
+
+  // BlockOnPromise fast path requires the passed onFulfilled function doesn't
+  // return an object value, because otherwise the skipped promise creation is
+  // detectable due to missing property lookups.
+  bool isDefaultResolveFn =
+      IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction);
+
+  // Steps 6-8.
+  if (!CommonPerformPromiseCombinator(
+          cx, iterator, C, resultCapability.promise(), done, isDefaultResolveFn,
+          getResolveAndReject)) {
+    return false;
+  }
+
+  // Step 8.d.ii.
+  int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+  // Step 8.d.iii.
+  if (remainingCount == 0) {
+    ThrowAggregateError(cx, errors, resultCapability.promise());
+    return false;
+  }
+
+  // Step 8.d.iv.
+  return true;
+}
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// Promise.any Reject Element Functions
+static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
+                                            Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  HandleValue xVal = args.get(0);
+
+  // Steps 1-5.
+  Rooted<PromiseCombinatorDataHolder*> data(cx);
+  uint32_t index;
+  if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
+    args.rval().setUndefined();
+    return true;
+  }
+
+  // Step 6.
+  Rooted<PromiseCombinatorElements> errors(cx);
+  if (!GetPromiseCombinatorElements(cx, data, &errors)) {
+    return false;
+  }
+
+  // Step 9.
+  if (!errors.setElement(cx, index, xVal)) {
+    return false;
+  }
+
+  // Steps 8, 10.
+  uint32_t remainingCount = data->decreaseRemainingCount();
+
+  // Step 11.
+  if (remainingCount == 0) {
+    // Step 7 (Adapted to work with PromiseCombinatorDataHolder's layout).
+    RootedObject rejectFun(cx, data->resolveOrRejectObj());
+    RootedObject promiseObj(cx, data->promiseObj());
+
+    ThrowAggregateError(cx, errors, promiseObj);
+
+    RootedValue reason(cx);
+    if (!MaybeGetAndClearException(cx, &reason)) {
+      return false;
+    }
+
+    if (!RunResolutionFunction(cx, rejectFun, reason, RejectMode, promiseObj)) {
+      return false;
+    }
+  }
+
+  // Step 12.
+  args.rval().setUndefined();
+  return true;
+}
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// ThrowAggregateError ( errors )
+static void ThrowAggregateError(JSContext* cx,
+                                Handle<PromiseCombinatorElements> errors,
+                                HandleObject promise) {
+  MOZ_ASSERT(!cx->isExceptionPending());
+
+  // Create the AggregateError in the same realm as the array object.
+  AutoRealm ar(cx, errors.unwrappedArray());
+
+  RootedObject allocationSite(cx);
+  mozilla::Maybe<JS::AutoSetAsyncStackForNewCalls> asyncStack;
+
+  // Provide a more useful error stack if possible: This function is typically
+  // called from Promise job queue, which doesn't have any JS frames on the
+  // stack. So when we create the AggregateError below, its stack property will
+  // be set to the empty string, which makes it harder to debug the error cause.
+  // To avoid this situation set-up an async stack based on the Promise
+  // allocation site, which should point to calling site of |Promise.any|.
+  if (promise->is<PromiseObject>()) {
+    allocationSite = promise->as<PromiseObject>().allocationSite();
+    if (allocationSite) {
+      asyncStack.emplace(
+          cx, allocationSite, "Promise.any",
+          JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
+    }
+  }
+
+  // AutoSetAsyncStackForNewCalls requires a new activation before it takes
+  // effect, so call into the self-hosting helper to set-up new call frames.
+  RootedValue error(cx);
+  if (!GetAggregateError(cx, JSMSG_PROMISE_ANY_REJECTION, &error)) {
+    return;
+  }
+
+  // |error| isn't guaranteed to be an AggregateErrorObject in case of OOM.
+  RootedSavedFrame stack(cx);
+  if (error.isObject() && error.toObject().is<AggregateErrorObject>()) {
+    auto* aggregateError = &error.toObject().as<AggregateErrorObject>();
+    aggregateError->setAggregateErrors(errors.unwrappedArray());
+
+    // Adopt the existing saved frames when present.
+    if (JSObject* errorStack = aggregateError->stack()) {
+      stack = &errorStack->as<SavedFrame>();
+    }
+  }
+
+  cx->setPendingException(error, stack);
+}
+
 // https://tc39.github.io/ecma262/#sec-promise.reject
 //
 // Unified implementation of
 // 25.6.4.4 Promise.reject ( r )
 // 25.6.4.5 Promise.resolve ( x )
 // 25.6.4.5.1 PromiseResolve ( C, x )
 static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(
     JSContext* cx, HandleValue thisVal, HandleValue argVal,
@@ -5832,16 +6068,19 @@ static const JSFunctionSpec promise_meth
     JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0), JS_FS_END};
 
 static const JSPropertySpec promise_properties[] = {
     JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY), JS_PS_END};
 
 static const JSFunctionSpec promise_static_methods[] = {
     JS_FN("all", Promise_static_all, 1, 0),
     JS_FN("allSettled", Promise_static_allSettled, 1, 0),
+#ifdef NIGHTLY_BUILD
+    JS_FN("any", Promise_static_any, 1, 0),
+#endif
     JS_FN("race", Promise_static_race, 1, 0),
     JS_FN("reject", Promise_reject, 1, 0),
     JS_FN("resolve", Promise_static_resolve, 1, 0),
     JS_FS_END};
 
 static const JSPropertySpec promise_static_properties[] = {
     JS_SYM_GET(species, Promise_static_species, 0), JS_PS_END};
 
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -172,16 +172,25 @@ function GetTypeError(msg) {
     try {
         FUN_APPLY(ThrowTypeError, undefined, arguments);
     } catch (e) {
         return e;
     }
     assert(false, "the catch block should've returned from this function.");
 }
 
+function GetAggregateError(msg) {
+    try {
+        FUN_APPLY(ThrowAggregateError, undefined, arguments);
+    } catch (e) {
+        return e;
+    }
+    assert(false, "the catch block should've returned from this function.");
+}
+
 function GetInternalError(msg) {
     try {
         FUN_APPLY(ThrowInternalError, undefined, arguments);
     } catch (e) {
         return e;
     }
     assert(false, "the catch block should've returned from this function.");
 }
copy from js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js
copy to js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js
--- a/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js
+++ b/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js
@@ -1,8 +1,10 @@
+// |jit-test| skip-if: !Promise.any
+
 function newPromiseCapability() {
     var resolve, reject, promise = new Promise(function(r1, r2) {
         resolve = r1;
         reject = r2;
     });
     return {promise, resolve, reject};
 }
 
@@ -42,15 +44,15 @@ class P extends Promise {
     // Default to the standard Promise.resolve function, so we don't create
     // another instance of this class when resolving the passed promise objects
     // in Promise.race.
     static resolve(v) {
         return Promise.resolve(v);
     }
 }
 
-P.race([promise]);
+P.any([promise]);
 
 resolve(0);
 
 drainJobQueue();
 
 assertEq(getterCount, 1);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -639,16 +639,17 @@ MSG_DEF(JSMSG_DYNAMIC_IMPORT_FAILED,    
 MSG_DEF(JSMSG_BAD_MODULE_SPECIFIER,      1, JSEXN_TYPEERR, "error resolving module specifier '{0}'")
 
 // Promise
 MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF,       0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
 MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
+MSG_DEF(JSMSG_PROMISE_ANY_REJECTION, 0, JSEXN_AGGREGATEERR, "No Promise in Promise.any was resolved")
 
 // Iterator
 MSG_DEF(JSMSG_RETURN_NOT_CALLABLE,     0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
 MSG_DEF(JSMSG_ITERATOR_NO_THROW,       0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
 
 // Async Function
 MSG_DEF(JSMSG_UNHANDLABLE_PROMISE_REJECTION_WARNING, 0, JSEXN_WARN, "unhandlable error after resolving async function's promise")
 
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -786,8 +786,16 @@ bool js::GetInternalError(JSContext* cx,
 
 bool js::GetTypeError(JSContext* cx, unsigned errorNumber,
                       MutableHandleValue error) {
   FixedInvokeArgs<1> args(cx);
   args[0].set(Int32Value(errorNumber));
   return CallSelfHostedFunction(cx, cx->names().GetTypeError, NullHandleValue,
                                 args, error);
 }
+
+bool js::GetAggregateError(JSContext* cx, unsigned errorNumber,
+                           MutableHandleValue error) {
+  FixedInvokeArgs<1> args(cx);
+  args[0].set(Int32Value(errorNumber));
+  return CallSelfHostedFunction(cx, cx->names().GetAggregateError,
+                                NullHandleValue, args, error);
+}
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -114,12 +114,14 @@ class AutoClearPendingException {
 
 extern const char* ValueToSourceForError(JSContext* cx, HandleValue val,
                                          JS::UniqueChars& bytes);
 
 bool GetInternalError(JSContext* cx, unsigned errorNumber,
                       MutableHandleValue error);
 bool GetTypeError(JSContext* cx, unsigned errorNumber,
                   MutableHandleValue error);
+bool GetAggregateError(JSContext* cx, unsigned errorNumber,
+                       MutableHandleValue error);
 
 }  // namespace js
 
 #endif /* jsexn_h */
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Promise/any-stack.js
@@ -0,0 +1,69 @@
+// |reftest| skip-if(!Promise.any)
+
+function toMessage(stack) {
+  // Provide the stack string in the error message for debugging.
+  return `[stack: ${stack.replace(/\n/g, "\\n")}]`;
+}
+
+// Test when AggregateError isn't created from a Promise Job.
+{
+  let p = Promise.any([]); // line 10
+
+  p.then(v => {
+    reportCompare(0, 1, "expected error");
+  }, e => {
+    assertEq(e.name, "AggregateError");
+    var {stack} = e;
+
+    assertEq(/^@.+any-stack.js:10/m.test(stack), true, toMessage(stack));
+  });
+}
+
+// Same as above, but now with surrounding function context.
+function testNoJobQueue() {
+  let p = Promise.any([]); // line 24
+
+  p.then(v => {
+    reportCompare(0, 1, "expected error");
+  }, e => {
+    assertEq(e.name, "AggregateError");
+    var {stack} = e;
+
+    assertEq(/^testNoJobQueue@.+any-stack.js:24/m.test(stack), true, toMessage(stack));
+  });
+}
+testNoJobQueue();
+
+// Test when AggregateError is created from a Promise Job.
+{
+  let rejected = Promise.reject(0);
+  let p = Promise.any([rejected]); // line 40
+
+  p.then(v => {
+    reportCompare(0, 1, "expected error");
+  }, e => {
+    assertEq(e.name, "AggregateError");
+    var {stack} = e;
+
+    assertEq(/^Promise.any\*@.+any-stack.js:40/m.test(stack), true, toMessage(stack));
+  });
+}
+
+// Same as above, but now with surrounding function context.
+function testFromJobQueue() {
+  let rejected = Promise.reject(0);
+  let p = Promise.any([rejected]); // line 55
+
+  p.then(v => {
+    reportCompare(0, 1, "expected error");
+  }, e => {
+    assertEq(e.name, "AggregateError");
+    var {stack} = e;
+
+    assertEq(/^Promise.any\*testFromJobQueue@.+any-stack.js:55/m.test(stack), true, toMessage(stack));
+  });
+}
+testFromJobQueue();
+
+if (typeof reportCompare === "function")
+  reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Promise/any.js
@@ -0,0 +1,78 @@
+// |reftest| skip-if(!Promise.any)
+
+// Smoke test for `Promise.any`, test262 should cover the function in
+// more detail.
+
+function expectedError() {
+  reportCompare(true, false, "expected error");
+}
+
+// Empty elements.
+Promise.any([]).then(expectedError, e => {
+  assertEq(e instanceof AggregateError, true);
+  assertEq(e.errors.length, 0);
+});
+
+// Single element.
+Promise.any([Promise.resolve(0)]).then(v => {
+  assertEq(v, 0);
+});
+Promise.any([Promise.reject(1)]).then(expectedError, e => {
+  assertEq(e instanceof AggregateError, true);
+  assertEq(e.errors.length, 1);
+  assertEq(e.errors[0], 1);
+});
+
+// Multiple elements.
+Promise.any([Promise.resolve(1), Promise.resolve(2)]).then(v => {
+  assertEq(v, 1);
+});
+Promise.any([Promise.resolve(3), Promise.reject(4)]).then(v => {
+  assertEq(v, 3);
+});
+Promise.any([Promise.reject(5), Promise.resolve(6)]).then(v => {
+  assertEq(v, 6);
+});
+Promise.any([Promise.reject(7), Promise.reject(8)]).then(expectedError, e => {
+  assertEq(e instanceof AggregateError, true);
+  assertEq(e.errors.length, 2);
+  assertEq(e.errors[0], 7);
+  assertEq(e.errors[1], 8);
+});
+
+// Cross-Realm tests.
+//
+// Note: When |g| is a cross-compartment global, Promise.any creates the errors
+// array and the AggregateError in |g|'s Realm. This doesn't follow the spec, but
+// the code in js/src/builtin/Promise.cpp claims this is useful when the Promise
+// compartment is less-privileged. This means for this test we can't use
+// assertDeepEq below, because the result array/error may have the wrong prototype.
+let g = newGlobal();
+
+if (typeof isSameCompartment !== "function") {
+  var isSameCompartment = SpecialPowers.Cu.getJSTestingFunctions().isSameCompartment;
+}
+
+// Test wrapping when no `Promise.any Reject Element Function` is called.
+Promise.any.call(g.Promise, []).then(expectedError, e => {
+  assertEq(e.name, "AggregateError");
+
+  assertEq(isSameCompartment(e, g), true);
+  assertEq(isSameCompartment(e.errors, g), true);
+
+  assertEq(e.errors.length, 0);
+});
+
+// Test wrapping in `Promise.any Reject Element Function`.
+Promise.any.call(g.Promise, [Promise.reject("err")]).then(expectedError, e => {
+  assertEq(e.name, "AggregateError");
+
+  assertEq(isSameCompartment(e, g), true);
+  assertEq(isSameCompartment(e.errors, g), true);
+
+  assertEq(e.errors.length, 1);
+  assertEq(e.errors[0], "err");
+});
+
+if (typeof reportCompare === "function")
+  reportCompare(0, 0);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -180,16 +180,17 @@
   MACRO(futexOK, futexOK, "ok")                                                \
   MACRO(futexTimedOut, futexTimedOut, "timed-out")                             \
   MACRO(gcCycleNumber, gcCycleNumber, "gcCycleNumber")                         \
   MACRO(Generator, Generator, "Generator")                                     \
   MACRO(GeneratorNext, GeneratorNext, "GeneratorNext")                         \
   MACRO(GeneratorReturn, GeneratorReturn, "GeneratorReturn")                   \
   MACRO(GeneratorThrow, GeneratorThrow, "GeneratorThrow")                      \
   MACRO(get, get, "get")                                                       \
+  MACRO(GetAggregateError, GetAggregateError, "GetAggregateError")             \
   MACRO(GetInternalError, GetInternalError, "GetInternalError")                \
   MACRO(getBigInt64, getBigInt64, "getBigInt64")                               \
   MACRO(getBigUint64, getBigUint64, "getBigUint64")                            \
   MACRO(getInternals, getInternals, "getInternals")                            \
   MACRO(GetModuleNamespace, GetModuleNamespace, "GetModuleNamespace")          \
   MACRO(getOwnPropertyDescriptor, getOwnPropertyDescriptor,                    \
         "getOwnPropertyDescriptor")                                            \
   MACRO(getOwnPropertyNames, getOwnPropertyNames, "getOwnPropertyNames")       \
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -386,16 +386,25 @@ static bool intrinsic_ThrowSyntaxError(J
                                        Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() >= 1);
 
   ThrowErrorWithType(cx, JSEXN_SYNTAXERR, args);
   return false;
 }
 
+static bool intrinsic_ThrowAggregateError(JSContext* cx, unsigned argc,
+                                          Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() >= 1);
+
+  ThrowErrorWithType(cx, JSEXN_AGGREGATEERR, args);
+  return false;
+}
+
 static bool intrinsic_ThrowInternalError(JSContext* cx, unsigned argc,
                                          Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() >= 1);
 
   ThrowErrorWithType(cx, JSEXN_INTERNALERR, args);
   return false;
 }
@@ -2164,16 +2173,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_INLINABLE_FN("IsConstructor", intrinsic_IsConstructor, 1, 0,
                     IntrinsicIsConstructor),
     JS_FN("GetBuiltinConstructorImpl", intrinsic_GetBuiltinConstructor, 1, 0),
     JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2, 0),
     JS_FN("_ConstructFunction", intrinsic_ConstructFunction, 2, 0),
     JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4, 0),
     JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4, 0),
     JS_FN("ThrowSyntaxError", intrinsic_ThrowSyntaxError, 4, 0),
+    JS_FN("ThrowAggregateError", intrinsic_ThrowAggregateError, 4, 0),
     JS_FN("ThrowInternalError", intrinsic_ThrowInternalError, 4, 0),
     JS_FN("GetErrorMessage", intrinsic_GetErrorMessage, 1, 0),
     JS_FN("CreateModuleSyntaxError", intrinsic_CreateModuleSyntaxError, 4, 0),
     JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1, 0),
     JS_FN("DumpMessage", intrinsic_DumpMessage, 1, 0),
     JS_FN("MakeDefaultConstructor", intrinsic_MakeDefaultConstructor, 2, 0),
     JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,
           0),