transitioning macro GeneratePromiseAll<F1: type, F2: type>( implicit context: Context)( receiver: JSAny, iterable: JSAny, createResolveElementFunctor: F1, createRejectElementFunctor: F2): JSAny { const nativeContext = LoadNativeContext(context); // Let C be the this value. // If Type(C) is not Object, throw a TypeError exception. const receiver = Cast<JSReceiver>(receiver) otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.all');
// Let promiseCapability be ? NewPromiseCapability(C). // Don't fire debugEvent so that forwarding the rejection through all does // not trigger redundant ExceptionEvents const capability = NewPromiseCapability(receiver, False);
// NewPromiseCapability guarantees that receiver is Constructor. assert(Is<Constructor>(receiver)); const constructor = UnsafeCast<Constructor>(receiver);
try { // Let promiseResolve be GetPromiseResolve(C). // IfAbruptRejectPromise(promiseResolve, promiseCapability). const promiseResolveFunction = GetPromiseResolve(nativeContext, constructor);
// Let iterator be GetIterator(iterable). // IfAbruptRejectPromise(iterator, promiseCapability). let i = iterator::GetIterator(iterable);
// Let result be PerformPromiseAll(iteratorRecord, C, // promiseCapability). If result is an abrupt completion, then // If iteratorRecord.[[Done]] is false, let result be // IteratorClose(iterator, result). // IfAbruptRejectPromise(result, promiseCapability). returnPerformPromiseAll( nativeContext, i, constructor, capability, promiseResolveFunction, createResolveElementFunctor, createRejectElementFunctor) otherwise Reject; } catch (e) deferred { goto Reject(e); } label Reject(e: Object) deferred { // Exception must be bound to a JS value. const e = UnsafeCast<JSAny>(e); const reject = UnsafeCast<JSAny>(capability.reject); Call(context, reject, Undefined, e); return capability.promise; } }
let i = iterator::GetIterator(iterable); returnPerformPromiseAll( nativeContext, i, constructor, capability, promiseResolveFunction, createResolveElementFunctor, createRejectElementFunctor) otherwise Reject;
transitioning macro PromiseAllResolveElementClosure<F: type>( implicit context: Context)( value: JSAny, function: JSFunction, wrapResultFunctor: F): JSAny { // We use the {function}s context as the marker to remember whether this // resolve element closure was already called. It points to the resolve // element context (which is a FunctionContext) until it was called the // first time, in which case we make it point to the native context here // to mark this resolve element closure as done. if (IsNativeContext(context)) deferred { returnUndefined; } assert( context.length == PromiseAllResolveElementContextSlots::kPromiseAllResolveElementLength); const nativeContext = LoadNativeContext(context); function.context = nativeContext;
// Update the value depending on whether Promise.all or // Promise.allSettled is called. const updatedValue = wrapResultFunctor.Call(nativeContext, value);
// Determine the index from the {function}. assert(kPropertyArrayNoHashSentinel == 0); const identityHash = LoadJSReceiverIdentityHash(function) otherwise unreachable; assert(identityHash > 0); const index = identityHash - 1;
// Check if we need to grow the [[ValuesArray]] to store {value} at {index}. const valuesArray = UnsafeCast<JSArray>( context[PromiseAllResolveElementContextSlots:: kPromiseAllResolveElementValuesArraySlot]); const elements = UnsafeCast<FixedArray>(valuesArray.elements); const valuesLength = Convert<intptr>(valuesArray.length); if (index < valuesLength) { // The {index} is in bounds of the {values_array}, // just store the {value} and continue. elements.objects[index] = updatedValue; } else { // Check if we need to grow the backing store. const newLength = index + 1; const elementsLength = elements.length_intptr; if (index < elementsLength) { // The {index} is within bounds of the {elements} backing store, so // just store the {value} and update the "length" of the {values_array}. valuesArray.length = Convert<Smi>(newLength); elements.objects[index] = updatedValue; } else deferred { // We need to grow the backing store to fit the {index} as well. const newElementsLength = IntPtrMin( CalculateNewElementsCapacity(newLength), kPropertyArrayHashFieldMax + 1); assert(index < newElementsLength); assert(elementsLength < newElementsLength); const newElements = ExtractFixedArray(elements, 0, elementsLength, newElementsLength); newElements.objects[index] = updatedValue;
// Check if we need to grow the [[ValuesArray]] to store {value} at {index}. const valuesArray = UnsafeCast<JSArray>( context[PromiseAllResolveElementContextSlots:: kPromiseAllResolveElementValuesArraySlot]); const elements = UnsafeCast<FixedArray>(valuesArray.elements); const valuesLength = Convert<intptr>(valuesArray.length); if (index < valuesLength) { // The {index} is in bounds of the {values_array}, // just store the {value} and continue. Print("elements--------------------------->"); Print(elements); Print("updatedValue----------------------->"); Print(updatedValue); elements.objects[index] = updatedValue; Print("elements--------------------------->"); Print(elements); } else {
// FixedArray describes fixed-sized arrays with element type Object. classFixedArray : public TorqueGeneratedFixedArray<FixedArray, FixedArrayBase> { public: // Setter and getter for elements. inline Objectget(int index) const; inline Objectget(constIsolate* isolate, int index) const;
static inline Handle<Object> get(FixedArray array, int index, Isolate* isolate);
// Return a grown copy if the index is bigger than the array's length. V8_EXPORT_PRIVATEstaticHandle<FixedArray> SetAndGrow( Isolate* isolate, Handle<FixedArray> array, int index, Handle<Object> value);
// Setter that uses write barrier. inline voidset(int index, Object value); inline bool is_the_hole(Isolate* isolate, int index);
// Setter that doesn't need write barrier. inline voidset(int index, Smi value); // Setter with explicit barrier mode. inline voidset(int index, Object value, WriteBarrierMode mode);
// Setters for frequently used oddballs located in old space. inline voidset_undefined(int index); inline voidset_undefined(Isolate* isolate, int index); inline voidset_null(int index); inline voidset_null(Isolate* isolate, int index); inline voidset_the_hole(int index); inline voidset_the_hole(Isolate* isolate, int index);
// Gives access to raw memory which stores the array's data. inline ObjectSlotdata_start();
inline voidMoveElements(Isolate* isolate, int dst_index, int src_index, int len, WriteBarrierMode mode);
inline voidCopyElements(Isolate* isolate, int dst_index, FixedArray src, int src_index, int len, WriteBarrierMode mode);
inline voidFillWithHoles(int from, int to);
// Shrink the array and insert filler objects. {new_length} must be > 0. V8_EXPORT_PRIVATEvoidShrink(Isolate* isolate, int new_length); // If {new_length} is 0, return the canonical empty FixedArray. Otherwise // like above. staticHandle<FixedArray> ShrinkOrEmpty(Isolate* isolate, Handle<FixedArray> array, int new_length);
// Copy a sub array from the receiver to dest. V8_EXPORT_PRIVATEvoidCopyTo(int pos, FixedArray dest, int dest_pos, int len) const;
// Maximally allowed length of a FixedArray. staticconst int kMaxLength = (kMaxSize - kHeaderSize) / kTaggedSize; static_assert(Internals::IsValidSmi(kMaxLength), "FixedArray maxLength not a Smi");
// Maximally allowed length for regular (non large object space) object. STATIC_ASSERT(kMaxRegularHeapObjectSize < kMaxSize); staticconst int kMaxRegularLength = (kMaxRegularHeapObjectSize - kHeaderSize) / kTaggedSize;
// Dispatched behavior. DECL_PRINTER(FixedArray)
int AllocatedSize();
classBodyDescriptor;
static constexpr int kObjectsOffset = kHeaderSize;
protected: // Set operation on FixedArray without using write barriers. Can // only be used for storing old space objects or smis. static inline voidNoWriteBarrierSet(FixedArray array, int index, Object value);
inline voidset_undefined(ReadOnlyRoots ro_roots, int index); inline voidset_null(ReadOnlyRoots ro_roots, int index); inline voidset_the_hole(ReadOnlyRoots ro_roots, int index);
classNumberDictionary : public Dictionary<NumberDictionary, NumberDictionaryShape> { public: DECL_CAST(NumberDictionary) DECL_PRINTER(NumberDictionary)
// Type specific at put (default NONE attributes is used when adding). V8_WARN_UNUSED_RESULTstaticHandle<NumberDictionary> Set( Isolate* isolate, Handle<NumberDictionary> dictionary, uint32_t key, Handle<Object> value, Handle<JSObject> dictionary_holder = Handle<JSObject>::null(), PropertyDetails details = PropertyDetails::Empty());
staticconst int kMaxNumberKeyIndex = kPrefixStartIndex; voidUpdateMaxNumberKey(uint32_t key, Handle<JSObject> dictionary_holder);
// Sorting support voidCopyValuesTo(FixedArray elements);
// If slow elements are required we will never go back to fast-case // for the elements kept in this dictionary. We require slow // elements if an element has been added at an index larger than // kRequiresSlowElementsLimit or set_requires_slow_elements() has been called // when defining a getter or setter with a number key. inline bool requires_slow_elements(); inline voidset_requires_slow_elements();
// Get the value of the max number key that has been added to this // dictionary. max_number_key can only be called if // requires_slow_elements returns false. inline uint32_t max_number_key();
staticconst int kEntryValueIndex = 1; staticconst int kEntryDetailsIndex = 2;
// Bit masks. staticconst int kRequiresSlowElementsMask = 1; staticconst int kRequiresSlowElementsTagSize = 1; staticconst uint32_t kRequiresSlowElementsLimit = (1 << 29) - 1;
// JSObjects prefer dictionary elements if the dictionary saves this much // memory compared to a fast elements backing store. staticconst uint32_t kPreferFastElementsSizeFactor = 3;
// Update the value depending on whether Promise.all or // Promise.allSettled is called. const updatedValue = wrapResultFunctor.Call(nativeContext, value); // ---> [3]
// Check if we need to grow the [[ValuesArray]] to store {value} at {index}. const valuesArray = UnsafeCast<JSArray>( context[PromiseAllResolveElementContextSlots:: kPromiseAllResolveElementValuesArraySlot]); const elements = UnsafeCast<FixedArray>(valuesArray.elements); // ---> [4] const valuesLength = Convert<intptr>(valuesArray.length); if (index < valuesLength) { // The {index} is in bounds of the {values_array}, // just store the {value} and continue. elements.objects[index] = updatedValue; // ---> [5] } // ... }
transitioning macro PromiseAllResolveElementClosure<F: type>( implicit context: Context)( - value: JSAny, function: JSFunction, wrapResultFunctor: F): JSAny { + value: JSAny, function: JSFunction, wrapResultFunctor: F, + hasResolveAndRejectClosures: constexpr bool): JSAny { // We use the {function}s context as the marker to remember whether this // resolve element closure was already called. It points to the resolve // element context (which is a FunctionContext) until it was called the @@ -98,10 +99,6 @@ const nativeContext = LoadNativeContext(context); function.context = nativeContext; - // Update the value depending on whether Promise.all or - // Promise.allSettled is called. - const updatedValue = wrapResultFunctor.Call(nativeContext, value); - // Determine the index from the {function}. assert(kPropertyArrayNoHashSentinel == 0); const identityHash = @@ -123,6 +120,27 @@ context.elements[PromiseAllResolveElementContextSlots:: kPromiseAllResolveElementValuesSlot] = values; } + + // Promise.allSettled, for each input element, has both a resolve and a reject + // closure that share an [[AlreadyCalled]] boolean. That is, the input element + // can only be settled once: after resolve is called, reject returns early, + // and vice versa. Using {function}'s context as the marker only tracks + // per-closure instead of per-element. When the second resolve/reject closure + // is called on the same index, values.object[index] will already exist and + // will not be the hole value. In that case, return early. Everything up to + // this point is not yet observable to user code. This is not a problem for + // Promise.all since Promise.all has a single resolve closure (no reject) per + // element. + if (hasResolveAndRejectClosures) { + if (values.objects[index] != TheHole) deferred { + returnUndefined; + } + } + + // Update the value depending on whether Promise.all or + // Promise.allSettled is called. + const updatedValue = wrapResultFunctor.Call(nativeContext, value); + values.objects[index] = updatedValue; remainingElementsCount = remainingElementsCount - 1; @@ -148,7 +166,7 @@ js-implicit context: Context, receiver: JSAny, target: JSFunction)(value: JSAny): JSAny { returnPromiseAllResolveElementClosure( - value, target, PromiseAllWrapResultAsFulfilledFunctor{}); + value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false); } transitioning javascript builtin @@ -156,7 +174,7 @@ js-implicit context: Context, receiver: JSAny, target: JSFunction)(value: JSAny): JSAny { returnPromiseAllResolveElementClosure( - value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}); + value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true); } transitioning javascript builtin @@ -164,6 +182,6 @@ js-implicit context: Context, receiver: JSAny, target: JSFunction)(value: JSAny): JSAny { returnPromiseAllResolveElementClosure( - value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}); + value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true); } }