diff --git a/BUILD.gn b/BUILD.gn index 24db06bfd2..ce5a76ba12 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -952,6 +952,7 @@ v8_source_set("v8_base") { "src/builtins/builtins-math.cc", "src/builtins/builtins-number.cc", "src/builtins/builtins-object.cc", + "src/builtins/builtins-promise.cc", "src/builtins/builtins-proxy.cc", "src/builtins/builtins-reflect.cc", "src/builtins/builtins-regexp.cc", diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index d26d6c5b26..c75961d4bb 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -3582,6 +3582,33 @@ bool Genesis::InstallNatives(GlobalContextType context_type) { *isolate()->builtins()->JSBuiltinsConstructStub()); InstallWithIntrinsicDefaultProto(isolate(), function, Context::PROMISE_FUNCTION_INDEX); + + { + Handle code = handle( + isolate()->builtins()->builtin(Builtins::kPromiseResolveClosure), + isolate()); + Handle info = + isolate()->factory()->NewSharedFunctionInfo(factory()->empty_string(), + code, false); + info->set_internal_formal_parameter_count(1); + info->set_length(1); + native_context()->set_promise_resolve_shared_fun(*info); + + code = handle( + isolate()->builtins()->builtin(Builtins::kPromiseRejectClosure), + isolate()); + info = isolate()->factory()->NewSharedFunctionInfo( + factory()->empty_string(), code, false); + info->set_internal_formal_parameter_count(2); + info->set_length(1); + native_context()->set_promise_reject_shared_fun(*info); + } + + Handle create_resolving_functions = + SimpleCreateFunction(isolate(), factory()->empty_string(), + Builtins::kCreateResolvingFunctions, 2, false); + native_context()->set_create_resolving_functions( + *create_resolving_functions); } InstallBuiltinFunctionIds(); diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc new file mode 100644 index 0000000000..987604930d --- /dev/null +++ b/src/builtins/builtins-promise.cc @@ -0,0 +1,113 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "src/builtins/builtins-utils.h" +#include "src/builtins/builtins.h" + +namespace v8 { +namespace internal { + +enum PromiseResolvingFunctionContextSlot { + kAlreadyVisitedSlot = Context::MIN_CONTEXT_SLOTS, + kPromiseSlot, + kDebugEventSlot, + kPromiseContextLength, +}; + +// ES#sec-promise-resolve-functions +// Promise Resolve Functions +BUILTIN(PromiseResolveClosure) { + HandleScope scope(isolate); + + Handle context(isolate->context(), isolate); + Handle already_visited(Smi::cast(context->get(kAlreadyVisitedSlot)), + isolate); + + if (already_visited->value() != 0) { + return isolate->heap()->undefined_value(); + } + + context->set(kAlreadyVisitedSlot, Smi::FromInt(1)); + Handle promise(JSObject::cast(context->get(kPromiseSlot)), isolate); + Handle value = args.atOrUndefined(isolate, 1); + + MaybeHandle maybe_result; + Handle argv[] = {promise, value}; + RETURN_FAILURE_ON_EXCEPTION( + isolate, Execution::Call(isolate, isolate->promise_resolve(), + isolate->factory()->undefined_value(), + arraysize(argv), argv)); + return isolate->heap()->undefined_value(); +} + +// ES#sec-promise-reject-functions +// Promise Reject Functions +BUILTIN(PromiseRejectClosure) { + HandleScope scope(isolate); + + Handle context(isolate->context(), isolate); + Handle already_visited(Smi::cast(context->get(kAlreadyVisitedSlot)), + isolate); + + if (already_visited->value() != 0) { + return isolate->heap()->undefined_value(); + } + + context->set(kAlreadyVisitedSlot, Smi::FromInt(1)); + + Handle value = args.atOrUndefined(isolate, 1); + Handle promise(JSObject::cast(context->get(kPromiseSlot)), isolate); + Handle debug_event(context->get(kDebugEventSlot), isolate); + MaybeHandle maybe_result; + Handle argv[] = {promise, value, debug_event}; + RETURN_FAILURE_ON_EXCEPTION( + isolate, Execution::Call(isolate, isolate->promise_internal_reject(), + isolate->factory()->undefined_value(), + arraysize(argv), argv)); + return isolate->heap()->undefined_value(); +} + +// ES#sec-createresolvingfunctions +// CreateResolvingFunctions ( promise ) +BUILTIN(CreateResolvingFunctions) { + HandleScope scope(isolate); + DCHECK_EQ(3, args.length()); + + Handle promise = args.at(1); + Handle debug_event = args.at(2); + + Handle context = + isolate->factory()->NewPromiseResolvingFunctionContext( + kPromiseContextLength); + context->set_native_context(*isolate->native_context()); + context->set(kAlreadyVisitedSlot, Smi::kZero); + context->set(kPromiseSlot, *promise); + context->set(kDebugEventSlot, *debug_event); + + Handle resolve_shared_fun( + isolate->native_context()->promise_resolve_shared_fun(), isolate); + Handle resolve = + isolate->factory()->NewFunctionFromSharedFunctionInfo( + isolate->sloppy_function_without_prototype_map(), resolve_shared_fun, + isolate->native_context(), TENURED); + + Handle reject_shared_fun( + isolate->native_context()->promise_reject_shared_fun(), isolate); + Handle reject = + isolate->factory()->NewFunctionFromSharedFunctionInfo( + isolate->sloppy_function_without_prototype_map(), reject_shared_fun, + isolate->native_context(), TENURED); + + resolve->set_context(*context); + reject->set_context(*context); + + Handle result = isolate->factory()->NewFixedArray(2); + result->set(0, *resolve); + result->set(1, *reject); + + return *isolate->factory()->NewJSArrayWithElements(result, FAST_ELEMENTS, 2, + NOT_TENURED); +} + +} // namespace internal +} // namespace v8 diff --git a/src/builtins/builtins.h b/src/builtins/builtins.h index 8de6ea1c2f..a310c8c440 100644 --- a/src/builtins/builtins.h +++ b/src/builtins/builtins.h @@ -561,6 +561,11 @@ namespace internal { TFS(InstanceOf, BUILTIN, kNoExtraICState, Compare) \ TFS(ForInFilter, BUILTIN, kNoExtraICState, ForInFilter) \ \ + /* Promise */ \ + CPP(CreateResolvingFunctions) \ + CPP(PromiseResolveClosure) \ + CPP(PromiseRejectClosure) \ + \ /* Proxy */ \ CPP(ProxyConstructor) \ CPP(ProxyConstructor_ConstructStub) \ diff --git a/src/contexts.h b/src/contexts.h index 64f883bb4d..a8b9dd2118 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -59,7 +59,8 @@ enum ContextLookupFlags { V(SPREAD_ARGUMENTS_INDEX, JSFunction, spread_arguments) \ V(SPREAD_ITERABLE_INDEX, JSFunction, spread_iterable) \ V(MATH_FLOOR_INDEX, JSFunction, math_floor) \ - V(MATH_POW_INDEX, JSFunction, math_pow) + V(MATH_POW_INDEX, JSFunction, math_pow) \ + V(CREATE_RESOLVING_FUNCTION_INDEX, JSFunction, create_resolving_functions) #define NATIVE_CONTEXT_IMPORTED_FIELDS(V) \ V(ARRAY_CONCAT_INDEX, JSFunction, array_concat) \ @@ -97,6 +98,7 @@ enum ContextLookupFlags { promise_has_user_defined_reject_handler) \ V(PROMISE_DEBUG_GET_INFO_INDEX, JSFunction, promise_debug_get_info) \ V(PROMISE_REJECT_INDEX, JSFunction, promise_reject) \ + V(PROMISE_INTERNAL_REJECT_INDEX, JSFunction, promise_internal_reject) \ V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \ V(PROMISE_THEN_INDEX, JSFunction, promise_then) \ V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \ @@ -273,6 +275,9 @@ enum ContextLookupFlags { V(PROXY_FUNCTION_INDEX, JSFunction, proxy_function) \ V(PROXY_FUNCTION_MAP_INDEX, Map, proxy_function_map) \ V(PROXY_MAP_INDEX, Map, proxy_map) \ + V(PROMISE_RESOLVE_SHARED_FUN, SharedFunctionInfo, \ + promise_resolve_shared_fun) \ + V(PROMISE_REJECT_SHARED_FUN, SharedFunctionInfo, promise_reject_shared_fun) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ V(REGEXP_LAST_MATCH_INFO_INDEX, RegExpMatchInfo, regexp_last_match_info) \ diff --git a/src/factory.cc b/src/factory.cc index f6b15708c7..b46ddf07ba 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -962,6 +962,14 @@ Handle Factory::NewBlockContext(Handle function, return context; } +Handle Factory::NewPromiseResolvingFunctionContext(int length) { + DCHECK_GE(length, Context::MIN_CONTEXT_SLOTS); + Handle array = NewFixedArray(length); + array->set_map_no_write_barrier(*function_context_map()); + Handle context = Handle::cast(array); + context->set_extension(*the_hole_value()); + return context; +} Handle Factory::NewStruct(InstanceType type) { CALL_HEAP_FUNCTION( diff --git a/src/factory.h b/src/factory.h index 023ec5f042..5bf32a5f19 100644 --- a/src/factory.h +++ b/src/factory.h @@ -310,6 +310,8 @@ class V8_EXPORT_PRIVATE Factory final { Handle NewBlockContext(Handle function, Handle previous, Handle scope_info); + // Create a promise context. + Handle NewPromiseResolvingFunctionContext(int length); // Allocate a new struct. The struct is pretenured (allocated directly in // the old generation). diff --git a/src/js/async-await.js b/src/js/async-await.js index 523b1c804c..a1cac0d5cd 100644 --- a/src/js/async-await.js +++ b/src/js/async-await.js @@ -130,7 +130,7 @@ function AsyncFunctionAwaitCaught(generator, awaited, outerPromise) { // How the parser rejects promises from async/await desugaring function RejectPromiseNoDebugEvent(promise, reason) { - return RejectPromise(promise, reason); + return RejectPromise(promise, reason, false); } function AsyncFunctionPromiseCreate() { diff --git a/src/js/promise.js b/src/js/promise.js index 75ec6e1c57..232317bb2e 100644 --- a/src/js/promise.js +++ b/src/js/promise.js @@ -49,35 +49,8 @@ const kPending = 0; const kFulfilled = +1; const kRejected = +2; -// ES#sec-createresolvingfunctions -// CreateResolvingFunctions ( promise ) -function CreateResolvingFunctions(promise, debugEvent) { - var alreadyResolved = false; - - // ES#sec-promise-resolve-functions - // Promise Resolve Functions - var resolve = value => { - if (alreadyResolved === true) return; - alreadyResolved = true; - ResolvePromise(promise, value); - }; - - // ES#sec-promise-reject-functions - // Promise Reject Functions - var reject = reason => { - if (alreadyResolved === true) return; - alreadyResolved = true; - %PromiseReject(promise, reason, debugEvent); - PromiseSet(promise, kRejected, reason); - }; - - return { - __proto__: null, - resolve: resolve, - reject: reject - }; -} - +const kResolveCallback = 0; +const kRejectCallback = 1; // ES#sec-promise-executor // Promise ( executor ) @@ -92,13 +65,15 @@ var GlobalPromise = function Promise(executor) { var promise = PromiseInit(%_NewObject(GlobalPromise, new.target)); // Calling the reject function would be a new exception, so debugEvent = true - var callbacks = CreateResolvingFunctions(promise, true); + // TODO(gsathya): Remove container for callbacks when this is moved + // to CPP/TF. + var callbacks = %create_resolving_functions(promise, true); var debug_is_active = DEBUG_IS_ACTIVE; try { if (debug_is_active) %DebugPushPromise(promise); - executor(callbacks.resolve, callbacks.reject); + executor(callbacks[kResolveCallback], callbacks[kRejectCallback]); } %catch (e) { // Natives syntax to mark this catch block. - %_Call(callbacks.reject, UNDEFINED, e); + %_Call(callbacks[kRejectCallback], UNDEFINED, e); } finally { if (debug_is_active) %DebugPopPromise(); } @@ -291,13 +266,16 @@ function ResolvePromise(promise, resolution) { } if (IS_CALLABLE(then)) { - var callbacks = CreateResolvingFunctions(promise, false); + // TODO(gsathya): Remove container for callbacks when this is + // moved to CPP/TF. + var callbacks = %create_resolving_functions(promise, false); if (DEBUG_IS_ACTIVE && IsPromise(resolution)) { // Mark the dependency of the new promise on the resolution SET_PRIVATE(resolution, promiseHandledBySymbol, promise); } %EnqueuePromiseResolveThenableJob( - resolution, then, callbacks.resolve, callbacks.reject); + resolution, then, callbacks[kResolveCallback], + callbacks[kRejectCallback]); return; } } @@ -307,8 +285,8 @@ function ResolvePromise(promise, resolution) { } // Only used by async-await.js -function RejectPromise(promise, reason) { - %PromiseReject(promise, reason, false); +function RejectPromise(promise, reason, debugEvent) { + %PromiseReject(promise, reason, debugEvent); PromiseSet(promise, kRejected, reason); } @@ -324,11 +302,13 @@ function NewPromiseCapability(C, debugEvent) { if (C === GlobalPromise) { // Optimized case, avoid extra closure. var promise = PromiseCreate(); - var callbacks = CreateResolvingFunctions(promise, debugEvent); + // TODO(gsathya): Remove container for callbacks when this is + // moved to CPP/TF. + var callbacks = %create_resolving_functions(promise, debugEvent); return { promise: promise, - resolve: callbacks.resolve, - reject: callbacks.reject + resolve: callbacks[kResolveCallback], + reject: callbacks[kRejectCallback] }; } @@ -643,6 +623,8 @@ utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ "promise_create", PromiseCreate, "promise_has_user_defined_reject_handler", PromiseHasUserDefinedRejectHandler, "promise_reject", DoRejectPromise, + // TODO(gsathya): Remove this once we update the promise builtin. + "promise_internal_reject", RejectPromise, "promise_resolve", ResolvePromise, "promise_then", PromiseThen, "promise_handle", PromiseHandle, diff --git a/src/v8.gyp b/src/v8.gyp index b005521cf0..6e0cbb87ac 100644 --- a/src/v8.gyp +++ b/src/v8.gyp @@ -492,6 +492,7 @@ 'builtins/builtins-math.cc', 'builtins/builtins-number.cc', 'builtins/builtins-object.cc', + 'builtins/builtins-promise.cc', 'builtins/builtins-proxy.cc', 'builtins/builtins-reflect.cc', 'builtins/builtins-regexp.cc', diff --git a/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden b/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden index 45ea7e7cdb..ced8e167ee 100644 --- a/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden +++ b/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden @@ -79,7 +79,7 @@ bytecodes: [ B(Star), R(0), B(CreateArrayLiteral), U8(0), U8(0), U8(9), B(Star), R(1), - B(CallJSRuntime), U8(150), R(0), U8(2), + B(CallJSRuntime), U8(152), R(0), U8(2), /* 44 S> */ B(Return), ] constant pool: [