diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index ec0560bbb7..02cc0f15bc 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -2140,6 +2140,9 @@ void Genesis::InitializeGlobal(Handle global_object, InstallSpeciesGetter(promise_fun); + SimpleInstallFunction(promise_fun, "all", Builtins::kPromiseAll, 1, true, + DONT_ENUM); + SimpleInstallFunction(promise_fun, "resolve", Builtins::kPromiseResolve, 1, true, DONT_ENUM); @@ -2222,6 +2225,16 @@ void Genesis::InitializeGlobal(Handle global_object, info->set_length(1); native_context()->set_promise_reject_shared_fun(*info); } + + { + Handle code = + isolate->builtins()->PromiseAllResolveElementClosure(); + Handle info = + factory->NewSharedFunctionInfo(factory->empty_string(), code, false); + info->set_internal_formal_parameter_count(1); + info->set_length(1); + native_context()->set_promise_all_resolve_element_shared_fun(*info); + } } { // -- R e g E x p diff --git a/src/builtins/builtins-collections-gen.cc b/src/builtins/builtins-collections-gen.cc index 0420c63823..49e1619228 100644 --- a/src/builtins/builtins-collections-gen.cc +++ b/src/builtins/builtins-collections-gen.cc @@ -159,7 +159,7 @@ TF_BUILTIN(MapConstructor, CollectionsBuiltinsAssembler) { Node* const fast_iterator_result_map = LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); - VARIABLE(var_exception, MachineRepresentation::kTagged, UndefinedConstant()); + VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant()); Label loop(this), if_notobject(this), if_exception(this); Goto(&loop); @@ -199,7 +199,8 @@ TF_BUILTIN(MapConstructor, CollectionsBuiltinsAssembler) { BIND(&if_exception); { - iterator_assembler.IteratorClose(context, iterator, var_exception.value()); + iterator_assembler.IteratorCloseOnException(context, iterator, + &var_exception); } BIND(&if_notcallable); @@ -285,7 +286,7 @@ TF_BUILTIN(SetConstructor, CollectionsBuiltinsAssembler) { Node* const fast_iterator_result_map = LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); - VARIABLE(var_exception, MachineRepresentation::kTagged, UndefinedConstant()); + VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant()); Label loop(this), if_notobject(this), if_exception(this); Goto(&loop); @@ -307,7 +308,8 @@ TF_BUILTIN(SetConstructor, CollectionsBuiltinsAssembler) { BIND(&if_exception); { - iterator_assembler.IteratorClose(context, iterator, var_exception.value()); + iterator_assembler.IteratorCloseOnException(context, iterator, + &var_exception); } BIND(&if_notcallable); diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 0ff8627f7e..375469a70e 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -772,6 +772,7 @@ namespace internal { TFJ(PromiseResolveClosure, 1, kValue) \ /* ES #sec-promise-reject-functions */ \ TFJ(PromiseRejectClosure, 1, kValue) \ + TFJ(PromiseAllResolveElementClosure, 1, kValue) \ /* ES #sec-promise.prototype.then */ \ TFJ(PromiseThen, 2, kOnFullfilled, kOnRejected) \ /* ES #sec-promise.prototype.catch */ \ @@ -791,6 +792,8 @@ namespace internal { TFJ(PromiseCatchFinally, 1, kReason) \ TFJ(PromiseValueThunkFinally, 0) \ TFJ(PromiseThrowerFinally, 0) \ + /* ES #sec-promise.all */ \ + TFJ(PromiseAll, 1, kIterable) \ \ /* Proxy */ \ CPP(ProxyConstructor) \ @@ -1098,6 +1101,7 @@ namespace internal { V(AsyncGeneratorAwaitCaught) \ V(AsyncGeneratorAwaitUncaught) \ V(PerformNativePromiseThen) \ + V(PromiseAll) \ V(PromiseConstructor) \ V(PromiseHandle) \ V(PromiseResolve) \ diff --git a/src/builtins/builtins-iterator-gen.cc b/src/builtins/builtins-iterator-gen.cc index 3a87329483..d60cfb7128 100644 --- a/src/builtins/builtins-iterator-gen.cc +++ b/src/builtins/builtins-iterator-gen.cc @@ -9,11 +9,15 @@ namespace internal { using compiler::Node; -Node* IteratorBuiltinsAssembler::GetIterator(Node* context, Node* object) { +Node* IteratorBuiltinsAssembler::GetIterator(Node* context, Node* object, + Label* if_exception, + Variable* exception) { Node* method = GetProperty(context, object, factory()->iterator_symbol()); + GotoIfException(method, if_exception, exception); Callable callable = CodeFactory::Call(isolate()); Node* iterator = CallJS(callable, context, method, object); + GotoIfException(iterator, if_exception, exception); Label done(this), if_notobject(this, Label::kDeferred); GotoIf(TaggedIsSmi(iterator), &if_notobject); @@ -21,8 +25,10 @@ Node* IteratorBuiltinsAssembler::GetIterator(Node* context, Node* object) { BIND(&if_notobject); { - CallRuntime(Runtime::kThrowTypeError, context, - SmiConstant(MessageTemplate::kNotAnIterator), iterator); + Node* ret = + CallRuntime(Runtime::kThrowTypeError, context, + SmiConstant(MessageTemplate::kNotAnIterator), iterator); + GotoIfException(ret, if_exception, exception); Unreachable(); } @@ -32,25 +38,31 @@ Node* IteratorBuiltinsAssembler::GetIterator(Node* context, Node* object) { Node* IteratorBuiltinsAssembler::IteratorStep(Node* context, Node* iterator, Label* if_done, - Node* fast_iterator_result_map) { + Node* fast_iterator_result_map, + Label* if_exception, + Variable* exception) { DCHECK_NOT_NULL(if_done); // IteratorNext Node* next_method = GetProperty(context, iterator, factory()->next_string()); + GotoIfException(next_method, if_exception, exception); // 1. a. Let result be ? Invoke(iterator, "next", « »). Callable callable = CodeFactory::Call(isolate()); Node* result = CallJS(callable, context, next_method, iterator); + GotoIfException(result, if_exception, exception); // 3. If Type(result) is not Object, throw a TypeError exception. Label if_notobject(this, Label::kDeferred), return_result(this); GotoIf(TaggedIsSmi(result), &if_notobject); GotoIfNot(IsJSReceiver(result), &if_notobject); - Label if_generic(this); VARIABLE(var_done, MachineRepresentation::kTagged); if (fast_iterator_result_map != nullptr) { + // Fast iterator result case: + Label if_generic(this); + // 4. Return result. Node* map = LoadMap(result); GotoIfNot(WordEqual(map, fast_iterator_result_map), &if_generic); @@ -61,15 +73,16 @@ Node* IteratorBuiltinsAssembler::IteratorStep(Node* context, Node* iterator, CSA_ASSERT(this, IsBoolean(done)); var_done.Bind(done); Goto(&return_result); - } else { - Goto(&if_generic); + + BIND(&if_generic); } - BIND(&if_generic); + // Generic iterator result case: { // IteratorComplete // 2. Return ToBoolean(? Get(iterResult, "done")). Node* done = GetProperty(context, result, factory()->done_string()); + GotoIfException(done, if_exception, exception); var_done.Bind(done); Label to_boolean(this, Label::kDeferred); @@ -83,8 +96,10 @@ Node* IteratorBuiltinsAssembler::IteratorStep(Node* context, Node* iterator, BIND(&if_notobject); { - CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result); - Goto(if_done); + Node* ret = + CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result); + GotoIfException(ret, if_exception, exception); + Unreachable(); } BIND(&return_result); @@ -93,23 +108,28 @@ Node* IteratorBuiltinsAssembler::IteratorStep(Node* context, Node* iterator, } Node* IteratorBuiltinsAssembler::IteratorValue(Node* context, Node* result, - Node* fast_iterator_result_map) { + Node* fast_iterator_result_map, + Label* if_exception, + Variable* exception) { CSA_ASSERT(this, IsJSReceiver(result)); - Label exit(this), if_generic(this); + Label exit(this); VARIABLE(var_value, MachineRepresentation::kTagged); if (fast_iterator_result_map != nullptr) { + // Fast iterator result case: + Label if_generic(this); Node* map = LoadMap(result); GotoIfNot(WordEqual(map, fast_iterator_result_map), &if_generic); var_value.Bind(LoadObjectField(result, JSIteratorResult::kValueOffset)); Goto(&exit); - } else { - Goto(&if_generic); + + BIND(&if_generic); } - BIND(&if_generic); + // Generic iterator result case: { Node* value = GetProperty(context, result, factory()->value_string()); + GotoIfException(value, if_exception, exception); var_value.Bind(value); Goto(&exit); } @@ -118,46 +138,46 @@ Node* IteratorBuiltinsAssembler::IteratorValue(Node* context, Node* result, return var_value.value(); } -void IteratorBuiltinsAssembler::IteratorClose(Node* context, Node* iterator, - Node* exception) { +void IteratorBuiltinsAssembler::IteratorCloseOnException(Node* context, + Node* iterator, + Label* if_exception, + Variable* exception) { + // Perform ES #sec-iteratorclose when an exception occurs. This simpler + // algorithm does not include redundant steps which are never reachable from + // the spec IteratorClose algorithm. + DCHECK_NOT_NULL(if_exception); + DCHECK_NOT_NULL(exception); + CSA_ASSERT(this, IsNotTheHole(exception->value())); CSA_ASSERT(this, IsJSReceiver(iterator)); - VARIABLE(var_iter_exception, MachineRepresentation::kTagged, - UndefinedConstant()); - Label rethrow_exception(this); + // Let return be ? GetMethod(iterator, "return"). Node* method = GetProperty(context, iterator, factory()->return_string()); - GotoIf(Word32Or(IsUndefined(method), IsNull(method)), &rethrow_exception); + GotoIfException(method, if_exception, exception); - Label if_iter_exception(this), if_notobject(this); + // If return is undefined, return Completion(completion). + GotoIf(Word32Or(IsUndefined(method), IsNull(method)), if_exception); - Node* inner_result = - CallJS(CodeFactory::Call(isolate()), context, method, iterator); - - GotoIfException(inner_result, &if_iter_exception, &var_iter_exception); - GotoIfNot(IsUndefined(exception), &rethrow_exception); - - GotoIf(TaggedIsSmi(inner_result), &if_notobject); - Branch(IsJSReceiver(inner_result), &rethrow_exception, &if_notobject); - - BIND(&if_notobject); { - CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, - inner_result); - Unreachable(); - } + // Let innerResult be Call(return, iterator, « »). + // If an exception occurs, the original exception remains bound + Node* inner_result = + CallJS(CodeFactory::Call(isolate()), context, method, iterator); + GotoIfException(inner_result, if_exception, nullptr); - BIND(&if_iter_exception); - { - GotoIfNot(IsUndefined(exception), &rethrow_exception); - CallRuntime(Runtime::kReThrow, context, var_iter_exception.value()); - Unreachable(); + // (If completion.[[Type]] is throw) return Completion(completion). + Goto(if_exception); } +} - BIND(&rethrow_exception); - { - CallRuntime(Runtime::kReThrow, context, exception); - Unreachable(); - } +void IteratorBuiltinsAssembler::IteratorCloseOnException(Node* context, + Node* iterator, + Variable* exception) { + Label rethrow(this, Label::kDeferred); + IteratorCloseOnException(context, iterator, &rethrow, exception); + + BIND(&rethrow); + CallRuntime(Runtime::kReThrow, context, exception->value()); + Unreachable(); } } // namespace internal diff --git a/src/builtins/builtins-iterator-gen.h b/src/builtins/builtins-iterator-gen.h index b93abaa27b..0ed6077024 100644 --- a/src/builtins/builtins-iterator-gen.h +++ b/src/builtins/builtins-iterator-gen.h @@ -16,7 +16,8 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler { // https://tc39.github.io/ecma262/#sec-getiterator --- never used for // @@asyncIterator. - Node* GetIterator(Node* context, Node* object); + Node* GetIterator(Node* context, Node* object, Label* if_exception = nullptr, + Variable* exception = nullptr); // https://tc39.github.io/ecma262/#sec-iteratorstep // Returns `false` if the iterator is done, otherwise returns an @@ -24,17 +25,24 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler { // `fast_iterator_result_map` refers to the map for the JSIteratorResult // object, loaded from the native context. Node* IteratorStep(Node* context, Node* iterator, Label* if_done, - Node* fast_iterator_result_map = nullptr); + Node* fast_iterator_result_map = nullptr, + Label* if_exception = nullptr, + Variable* exception = nullptr); // https://tc39.github.io/ecma262/#sec-iteratorvalue // Return the `value` field from an iterator. // `fast_iterator_result_map` refers to the map for the JSIteratorResult // object, loaded from the native context. Node* IteratorValue(Node* context, Node* result, - Node* fast_iterator_result_map = nullptr); + Node* fast_iterator_result_map = nullptr, + Label* if_exception = nullptr, + Variable* exception = nullptr); // https://tc39.github.io/ecma262/#sec-iteratorclose - void IteratorClose(Node* context, Node* iterator, Node* exception); + void IteratorCloseOnException(Node* context, Node* iterator, + Label* if_exception, Variable* exception); + void IteratorCloseOnException(Node* context, Node* iterator, + Variable* exception); }; } // namespace internal diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc index 907e40a63b..7901dba3d7 100644 --- a/src/builtins/builtins-promise-gen.cc +++ b/src/builtins/builtins-promise-gen.cc @@ -5,6 +5,7 @@ #include "src/builtins/builtins-promise-gen.h" #include "src/builtins/builtins-constructor-gen.h" +#include "src/builtins/builtins-iterator-gen.h" #include "src/builtins/builtins-utils-gen.h" #include "src/builtins/builtins.h" #include "src/code-factory.h" @@ -208,7 +209,7 @@ Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context, int slots) { DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS); - Node* const context = Allocate(FixedArray::SizeFor(slots)); + Node* const context = AllocateInNewSpace(FixedArray::SizeFor(slots)); StoreMapNoWriteBarrier(context, Heap::kFunctionContextMapRootIndex); StoreObjectFieldNoWriteBarrier(context, FixedArray::kLengthOffset, SmiConstant(slots)); @@ -1818,5 +1819,329 @@ TF_BUILTIN(PerformNativePromiseThen, PromiseBuiltinsAssembler) { Return(result_promise); } +Node* PromiseBuiltinsAssembler::PerformPromiseAll( + Node* context, Node* constructor, Node* capability, Node* iterator, + Label* if_exception, Variable* var_exception) { + IteratorBuiltinsAssembler iter_assembler(state()); + Label close_iterator(this); + + Node* const instrumenting = IsDebugActive(); + + // For catch prediction, don't treat the .then calls as handling it; + // instead, recurse outwards. + { + Label did_set_forwarding_handler(this); + GotoIfNot(instrumenting, &did_set_forwarding_handler); + CallRuntime(Runtime::kSetProperty, context, + LoadObjectField(capability, JSPromiseCapability::kRejectOffset), + HeapConstant(factory()->promise_forwarding_handler_symbol()), + TrueConstant(), SmiConstant(STRICT)); + Goto(&did_set_forwarding_handler); + BIND(&did_set_forwarding_handler); + } + + Node* const native_context = LoadNativeContext(context); + Node* const array_map = LoadContextElement( + native_context, Context::JS_ARRAY_FAST_ELEMENTS_MAP_INDEX); + Node* const values_array = AllocateJSArray(FAST_ELEMENTS, array_map, + IntPtrConstant(0), SmiConstant(0)); + Node* const remaining_elements = AllocateSmiCell(1); + + VARIABLE(var_index, MachineRepresentation::kTagged, SmiConstant(0)); + + Label loop(this, &var_index), break_loop(this); + Goto(&loop); + BIND(&loop); + { + // Let next be IteratorStep(iteratorRecord.[[Iterator]]). + // If next is an abrupt completion, set iteratorRecord.[[Done]] to true. + // ReturnIfAbrupt(next). + Node* const fast_iterator_result_map = + LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); + Node* const next = iter_assembler.IteratorStep( + context, iterator, &break_loop, fast_iterator_result_map, if_exception, + var_exception); + + // Let nextValue be IteratorValue(next). + // If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to + // true. + // ReturnIfAbrupt(nextValue). + Node* const next_value = iter_assembler.IteratorValue( + context, next, fast_iterator_result_map, if_exception, var_exception); + + // Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »). + Node* const promise_resolve = + GetProperty(context, constructor, factory()->resolve_string()); + GotoIfException(promise_resolve, &close_iterator, var_exception); + + Node* const next_promise = CallJS(CodeFactory::Call(isolate()), context, + promise_resolve, constructor, next_value); + GotoIfException(next_promise, &close_iterator, var_exception); + + // Let resolveElement be a new built-in function object as defined in + // Promise.all Resolve Element Functions. + Node* const resolve_context = + CreatePromiseContext(native_context, kPromiseAllResolveElementLength); + StoreContextElementNoWriteBarrier( + resolve_context, kPromiseAllResolveElementAlreadyVisitedSlot, + SmiConstant(0)); + StoreContextElementNoWriteBarrier( + resolve_context, kPromiseAllResolveElementIndexSlot, var_index.value()); + StoreContextElementNoWriteBarrier( + resolve_context, kPromiseAllResolveElementRemainingElementsSlot, + remaining_elements); + StoreContextElementNoWriteBarrier( + resolve_context, kPromiseAllResolveElementCapabilitySlot, capability); + StoreContextElementNoWriteBarrier(resolve_context, + kPromiseAllResolveElementValuesArraySlot, + values_array); + + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const resolve_info = LoadContextElement( + native_context, Context::PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN); + Node* const resolve = + AllocateFunctionWithMapAndContext(map, resolve_info, resolve_context); + + // Set remainingElementsCount.[[Value]] to + // remainingElementsCount.[[Value]] + 1. + { + Label if_outofrange(this, Label::kDeferred), done(this); + IncrementSmiCell(remaining_elements, &if_outofrange); + Goto(&done); + + BIND(&if_outofrange); + { + // If the incremented value is out of Smi range, crash. + Abort(kOffsetOutOfRange); + } + + BIND(&done); + } + + // Perform ? Invoke(nextPromise, "then", « resolveElement, + // resultCapability.[[Reject]] »). + Node* const then = + GetProperty(context, next_promise, factory()->then_string()); + GotoIfException(then, &close_iterator, var_exception); + + Node* const then_call = CallJS( + CodeFactory::Call(isolate()), context, then, next_promise, resolve, + LoadObjectField(capability, JSPromiseCapability::kRejectOffset)); + GotoIfException(then_call, &close_iterator, var_exception); + + // For catch prediction, mark that rejections here are semantically + // handled by the combined Promise. + Label did_set_handled_by(this); + GotoIfNot(instrumenting, &did_set_handled_by); + GotoIf(TaggedIsSmi(then_call), &did_set_handled_by); + GotoIfNot(HasInstanceType(then_call, JS_PROMISE_TYPE), &did_set_handled_by); + CallRuntime( + Runtime::kSetProperty, context, then_call, + HeapConstant(factory()->promise_handled_by_symbol()), + LoadObjectField(capability, JSPromiseCapability::kPromiseOffset), + SmiConstant(STRICT)); + Goto(&did_set_handled_by); + BIND(&did_set_handled_by); + + // Set index to index + 1 + var_index.Bind(NumberInc(var_index.value())); + Goto(&loop); + } + + BIND(&close_iterator); + { + // Exception must be bound to a JS value. + CSA_ASSERT(this, IsNotTheHole(var_exception->value())); + iter_assembler.IteratorCloseOnException(context, iterator, if_exception, + var_exception); + } + + BIND(&break_loop); + { + Label resolve_promise(this), return_promise(this); + // Set iteratorRecord.[[Done]] to true. + // Set remainingElementsCount.[[Value]] to + // remainingElementsCount.[[Value]] - 1. + Node* const remaining = DecrementSmiCell(remaining_elements); + Branch(SmiEqual(remaining, SmiConstant(0)), &resolve_promise, + &return_promise); + + // If remainingElementsCount.[[Value]] is 0, then + // Let valuesArray be CreateArrayFromList(values). + // Perform ? Call(resultCapability.[[Resolve]], undefined, + // « valuesArray »). + BIND(&resolve_promise); + + Node* const resolve = + LoadObjectField(capability, JSPromiseCapability::kResolveOffset); + Node* const resolve_call = + CallJS(CodeFactory::Call(isolate()), context, resolve, + UndefinedConstant(), values_array); + GotoIfException(resolve_call, if_exception, var_exception); + Goto(&return_promise); + + // Return resultCapability.[[Promise]]. + BIND(&return_promise); + } + + Node* const promise = + LoadObjectField(capability, JSPromiseCapability::kPromiseOffset); + return promise; +} + +Node* PromiseBuiltinsAssembler::IncrementSmiCell(Node* cell, + Label* if_overflow) { + CSA_SLOW_ASSERT(this, HasInstanceType(cell, CELL_TYPE)); + Node* value = LoadCellValue(cell); + CSA_SLOW_ASSERT(this, TaggedIsSmi(value)); + + if (if_overflow != nullptr) { + GotoIf(SmiEqual(value, SmiConstant(Smi::kMaxValue)), if_overflow); + } + + Node* result = SmiAdd(value, SmiConstant(1)); + StoreCellValue(cell, result, SKIP_WRITE_BARRIER); + return result; +} + +Node* PromiseBuiltinsAssembler::DecrementSmiCell(Node* cell) { + CSA_SLOW_ASSERT(this, HasInstanceType(cell, CELL_TYPE)); + Node* value = LoadCellValue(cell); + CSA_SLOW_ASSERT(this, TaggedIsSmi(value)); + + Node* result = SmiSub(value, SmiConstant(1)); + StoreCellValue(cell, result, SKIP_WRITE_BARRIER); + return result; +} + +// ES#sec-promise.all +// Promise.all ( iterable ) +TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) { + IteratorBuiltinsAssembler iter_assembler(state()); + + // Let C be the this value. + // If Type(C) is not Object, throw a TypeError exception. + Node* const receiver = Parameter(Descriptor::kReceiver); + Node* const context = Parameter(Descriptor::kContext); + ThrowIfNotJSReceiver(context, receiver, 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 + Node* const debug_event = FalseConstant(); + Node* const capability = NewPromiseCapability(context, receiver, debug_event); + + VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant()); + Label reject_promise(this, &var_exception, Label::kDeferred); + + // Let iterator be GetIterator(iterable). + // IfAbruptRejectPromise(iterator, promiseCapability). + Node* const iterable = Parameter(Descriptor::kIterable); + Node* const iterator = iter_assembler.GetIterator( + context, iterable, &reject_promise, &var_exception); + + // 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). + Node* const result = PerformPromiseAll( + context, receiver, capability, iterator, &reject_promise, &var_exception); + + Return(result); + + BIND(&reject_promise); + { + // Exception must be bound to a JS value. + CSA_SLOW_ASSERT(this, IsNotTheHole(var_exception.value())); + Node* const reject = + LoadObjectField(capability, JSPromiseCapability::kRejectOffset); + Callable callable = CodeFactory::Call(isolate()); + CallJS(callable, context, reject, UndefinedConstant(), + var_exception.value()); + + Node* const promise = + LoadObjectField(capability, JSPromiseCapability::kPromiseOffset); + Return(promise); + } +} + +TF_BUILTIN(PromiseAllResolveElementClosure, PromiseBuiltinsAssembler) { + Node* const value = Parameter(Descriptor::kValue); + Node* const context = Parameter(Descriptor::kContext); + + CSA_ASSERT(this, SmiEqual(LoadFixedArrayBaseLength(context), + SmiConstant(kPromiseAllResolveElementLength))); + + Label already_called(this), resolve_promise(this); + GotoIf(SmiEqual(LoadContextElement( + context, kPromiseAllResolveElementAlreadyVisitedSlot), + SmiConstant(1)), + &already_called); + StoreContextElementNoWriteBarrier( + context, kPromiseAllResolveElementAlreadyVisitedSlot, SmiConstant(1)); + + Node* const index = + LoadContextElement(context, kPromiseAllResolveElementIndexSlot); + Node* const values_array = + LoadContextElement(context, kPromiseAllResolveElementValuesArraySlot); + + // Set element in FixedArray + Label runtime_set_element(this), did_set_element(this); + GotoIfNot(TaggedIsPositiveSmi(index), &runtime_set_element); + { + VARIABLE(var_elements, MachineRepresentation::kTagged, + LoadElements(values_array)); + PossiblyGrowElementsCapacity(SMI_PARAMETERS, FAST_ELEMENTS, values_array, + index, &var_elements, SmiConstant(1), + &runtime_set_element); + StoreFixedArrayElement(var_elements.value(), index, value, + UPDATE_WRITE_BARRIER, 0, SMI_PARAMETERS); + + // Update array length + Label did_set_length(this); + Node* const length = LoadJSArrayLength(values_array); + GotoIfNot(TaggedIsPositiveSmi(length), &did_set_length); + Node* const new_length = SmiAdd(index, SmiConstant(1)); + GotoIfNot(SmiLessThan(length, new_length), &did_set_length); + StoreObjectFieldNoWriteBarrier(values_array, JSArray::kLengthOffset, + new_length); + // Assert that valuesArray.[[Length]] is less than or equal to the + // elements backing-store length.e + CSA_SLOW_ASSERT( + this, SmiAboveOrEqual(LoadFixedArrayBaseLength(var_elements.value()), + new_length)); + Goto(&did_set_length); + BIND(&did_set_length); + } + Goto(&did_set_element); + BIND(&runtime_set_element); + // New-space filled up or index too large, set element via runtime + CallRuntime(Runtime::kCreateDataProperty, context, values_array, index, + value); + Goto(&did_set_element); + BIND(&did_set_element); + + Node* const remaining_elements = LoadContextElement( + context, kPromiseAllResolveElementRemainingElementsSlot); + Node* const result = DecrementSmiCell(remaining_elements); + GotoIf(SmiEqual(result, SmiConstant(0)), &resolve_promise); + Return(UndefinedConstant()); + + BIND(&resolve_promise); + Node* const capability = + LoadContextElement(context, kPromiseAllResolveElementCapabilitySlot); + Node* const resolve = + LoadObjectField(capability, JSPromiseCapability::kResolveOffset); + CallJS(CodeFactory::Call(isolate()), context, resolve, UndefinedConstant(), + values_array); + Return(UndefinedConstant()); + + BIND(&already_called); + Return(UndefinedConstant()); +} + } // namespace internal } // namespace v8 diff --git a/src/builtins/builtins-promise-gen.h b/src/builtins/builtins-promise-gen.h index a03132d6a6..d785a2857e 100644 --- a/src/builtins/builtins-promise-gen.h +++ b/src/builtins/builtins-promise-gen.h @@ -28,6 +28,27 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { kPromiseContextLength, }; + protected: + enum PromiseAllResolveElementContextSlots { + // Whether the resolve callback was already called. + kPromiseAllResolveElementAlreadyVisitedSlot = Context::MIN_CONTEXT_SLOTS, + + // Index into the values array + kPromiseAllResolveElementIndexSlot, + + // Remaining elements count (mutable HeapNumber) + kPromiseAllResolveElementRemainingElementsSlot, + + // Promise capability from Promise.all + kPromiseAllResolveElementCapabilitySlot, + + // Values array from Promise.all + kPromiseAllResolveElementValuesArraySlot, + + kPromiseAllResolveElementLength + }; + + public: enum FunctionContextSlot { kCapabilitySlot = Context::MIN_CONTEXT_SLOTS, @@ -135,6 +156,13 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { Node* CreateThrowerFunctionContext(Node* reason, Node* native_context); Node* CreateThrowerFunction(Node* reason, Node* native_context); + Node* PerformPromiseAll(Node* context, Node* constructor, Node* capability, + Node* iterator, Label* if_exception, + Variable* var_exception); + + Node* IncrementSmiCell(Node* cell, Label* if_overflow = nullptr); + Node* DecrementSmiCell(Node* cell); + private: Node* AllocateJSPromise(Node* context); }; diff --git a/src/code-stub-assembler.cc b/src/code-stub-assembler.cc index 203d5c34c0..c284ece8f0 100644 --- a/src/code-stub-assembler.cc +++ b/src/code-stub-assembler.cc @@ -153,9 +153,12 @@ Node* CodeStubAssembler::NoContextConstant() { return NumberConstant(0); } HEAP_CONSTANT_LIST(HEAP_CONSTANT_ACCESSOR); #undef HEAP_CONSTANT_ACCESSOR -#define HEAP_CONSTANT_TEST(rootName, name) \ - Node* CodeStubAssembler::Is##name(Node* value) { \ - return WordEqual(value, name##Constant()); \ +#define HEAP_CONSTANT_TEST(rootName, name) \ + Node* CodeStubAssembler::Is##name(Node* value) { \ + return WordEqual(value, name##Constant()); \ + } \ + Node* CodeStubAssembler::IsNot##name(Node* value) { \ + return WordNotEqual(value, name##Constant()); \ } HEAP_CONSTANT_LIST(HEAP_CONSTANT_TEST); #undef HEAP_CONSTANT_TEST @@ -1704,6 +1707,31 @@ void CodeStubAssembler::BuildAppendJSArray(ElementsKind kind, Node* array, StoreObjectFieldNoWriteBarrier(array, JSArray::kLengthOffset, length); } +Node* CodeStubAssembler::AllocateCellWithValue(Node* value, + WriteBarrierMode mode) { + Node* result = Allocate(Cell::kSize, kNone); + StoreMapNoWriteBarrier(result, Heap::kCellMapRootIndex); + StoreCellValue(result, value, mode); + return result; +} + +Node* CodeStubAssembler::LoadCellValue(Node* cell) { + CSA_SLOW_ASSERT(this, HasInstanceType(cell, CELL_TYPE)); + return LoadObjectField(cell, Cell::kValueOffset); +} + +Node* CodeStubAssembler::StoreCellValue(Node* cell, Node* value, + WriteBarrierMode mode) { + CSA_SLOW_ASSERT(this, HasInstanceType(cell, CELL_TYPE)); + DCHECK(mode == SKIP_WRITE_BARRIER || mode == UPDATE_WRITE_BARRIER); + + if (mode == UPDATE_WRITE_BARRIER) { + return StoreObjectField(cell, Cell::kValueOffset, value); + } else { + return StoreObjectFieldNoWriteBarrier(cell, Cell::kValueOffset, value); + } +} + Node* CodeStubAssembler::AllocateHeapNumber(MutableMode mode) { Node* result = Allocate(HeapNumber::kSize, kNone); Heap::RootListIndex heap_map_index = diff --git a/src/code-stub-assembler.h b/src/code-stub-assembler.h index 7bb40cfd05..55efd8af86 100644 --- a/src/code-stub-assembler.h +++ b/src/code-stub-assembler.h @@ -152,7 +152,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { HEAP_CONSTANT_LIST(HEAP_CONSTANT_ACCESSOR) #undef HEAP_CONSTANT_ACCESSOR -#define HEAP_CONSTANT_TEST(rootName, name) Node* Is##name(Node* value); +#define HEAP_CONSTANT_TEST(rootName, name) \ + Node* Is##name(Node* value); \ + Node* IsNot##name(Node* value); HEAP_CONSTANT_LIST(HEAP_CONSTANT_TEST) #undef HEAP_CONSTANT_TEST @@ -547,6 +549,17 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { void StoreFieldsNoWriteBarrier(Node* start_address, Node* end_address, Node* value); + Node* AllocateCellWithValue(Node* value, + WriteBarrierMode mode = UPDATE_WRITE_BARRIER); + Node* AllocateSmiCell(int value = 0) { + return AllocateCellWithValue(SmiConstant(value), SKIP_WRITE_BARRIER); + } + + Node* LoadCellValue(Node* cell); + + Node* StoreCellValue(Node* cell, Node* value, + WriteBarrierMode mode = UPDATE_WRITE_BARRIER); + // Allocate a HeapNumber without initializing its value. Node* AllocateHeapNumber(MutableMode mode = IMMUTABLE); // Allocate a HeapNumber with a specific value. @@ -1452,6 +1465,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { UndefinedConstant(), SmiConstant(message), args...); } + void Abort(BailoutReason reason) { + CallRuntime(Runtime::kAbort, NoContextConstant(), SmiConstant(reason)); + Unreachable(); + } + protected: void DescriptorLookup(Node* unique_name, Node* descriptors, Node* bitfield3, Label* if_found, Variable* var_name_index, diff --git a/src/compiler/code-assembler.cc b/src/compiler/code-assembler.cc index 1bfd72a63e..27073710e3 100644 --- a/src/compiler/code-assembler.cc +++ b/src/compiler/code-assembler.cc @@ -554,10 +554,16 @@ Node* CodeAssembler::Projection(int index, Node* value) { void CodeAssembler::GotoIfException(Node* node, Label* if_exception, Variable* exception_var) { + DCHECK(!node->op()->HasProperty(Operator::kNoThrow)); + + if (if_exception == nullptr) { + // If no handler is supplied, don't add continuations + return; + } + Label success(this), exception(this, Label::kDeferred); success.MergeVariables(); exception.MergeVariables(); - DCHECK(!node->op()->HasProperty(Operator::kNoThrow)); raw_assembler()->Continuations(node, success.label_, exception.label_); diff --git a/src/contexts.h b/src/contexts.h index c4af0070ac..8d92cd2925 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -321,6 +321,8 @@ enum ContextLookupFlags { promise_value_thunk_finally_shared_fun) \ V(PROMISE_THROWER_FINALLY_SHARED_FUN, SharedFunctionInfo, \ promise_thrower_finally_shared_fun) \ + V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \ + promise_all_resolve_element_shared_fun) \ V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ diff --git a/src/heap/objects-visiting-inl.h b/src/heap/objects-visiting-inl.h index 7f789d2cac..4397d579a4 100644 --- a/src/heap/objects-visiting-inl.h +++ b/src/heap/objects-visiting-inl.h @@ -40,6 +40,10 @@ void StaticNewSpaceVisitor::Initialize() { &FixedBodyVisitor::Visit); + table_.Register( + kVisitCell, + &FixedBodyVisitor::Visit); + table_.Register( kVisitSymbol, &FixedBodyVisitor::Visit); diff --git a/src/heap/scavenger.cc b/src/heap/scavenger.cc index e211388729..2e2bf42f07 100644 --- a/src/heap/scavenger.cc +++ b/src/heap/scavenger.cc @@ -32,6 +32,10 @@ class ScavengingVisitor : public StaticVisitorBase { table_.Register(kVisitSeqOneByteString, &EvacuateSeqOneByteString); table_.Register(kVisitSeqTwoByteString, &EvacuateSeqTwoByteString); table_.Register(kVisitShortcutCandidate, &EvacuateShortcutCandidate); + table_.Register( + kVisitCell, + &ObjectEvacuationStrategy::template VisitSpecialized< + Cell::kSize>); table_.Register(kVisitThinString, &EvacuateThinString); table_.Register(kVisitByteArray, &EvacuateByteArray); table_.Register(kVisitFixedArray, &EvacuateFixedArray); diff --git a/src/js/promise.js b/src/js/promise.js index 27571daabb..53167a7406 100644 --- a/src/js/promise.js +++ b/src/js/promise.js @@ -23,70 +23,6 @@ var GlobalPromise = global.Promise; // Combinators. -// ES#sec-promise.all -// Promise.all ( iterable ) -function PromiseAll(iterable) { - if (!IS_RECEIVER(this)) { - throw %make_type_error(kCalledOnNonObject, "Promise.all"); - } - - // false debugEvent so that forwarding the rejection through all does not - // trigger redundant ExceptionEvents - var deferred = %new_promise_capability(this, false); - var resolutions = new InternalArray(); - var count; - - // For catch prediction, don't treat the .then calls as handling it; - // instead, recurse outwards. - var instrumenting = DEBUG_IS_ACTIVE; - if (instrumenting) { - SET_PRIVATE(deferred.reject, promiseForwardingHandlerSymbol, true); - } - - function CreateResolveElementFunction(index, values, promiseCapability) { - var alreadyCalled = false; - return (x) => { - if (alreadyCalled === true) return; - alreadyCalled = true; - values[index] = x; - if (--count === 0) { - var valuesArray = []; - %MoveArrayContents(values, valuesArray); - %_Call(promiseCapability.resolve, UNDEFINED, valuesArray); - } - }; - } - - try { - var i = 0; - count = 1; - for (var value of iterable) { - var nextPromise = this.resolve(value); - ++count; - var throwawayPromise = nextPromise.then( - CreateResolveElementFunction(i, resolutions, deferred), - deferred.reject); - // For catch prediction, mark that rejections here are semantically - // handled by the combined Promise. - if (instrumenting && %is_promise(throwawayPromise)) { - SET_PRIVATE(throwawayPromise, promiseHandledBySymbol, deferred.promise); - } - ++i; - } - - // 6.d - if (--count === 0) { - var valuesArray = []; - %MoveArrayContents(resolutions, valuesArray); - %_Call(deferred.resolve, UNDEFINED, valuesArray); - } - - } catch (e) { - %_Call(deferred.reject, UNDEFINED, e); - } - return deferred.promise; -} - // ES#sec-promise.race // Promise.race ( iterable ) function PromiseRace(iterable) { @@ -125,7 +61,6 @@ function PromiseRace(iterable) { // Install exported functions. utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ - "all", PromiseAll, "race", PromiseRace, ]);