Commit b1c148b9 authored by gsathya's avatar gsathya Committed by Commit bot

[promisehook] Implement PromiseHook

This adds kInit, kResolve, kBefore and kAfter lifecycle hooks to promises.

This also exposes an API to set the PromiseHook.

BUG=v8:4643

Review-Url: https://codereview.chromium.org/2575313002
Cr-Commit-Position: refs/heads/master@{#41775}
parent ca8d3ba7
......@@ -5756,6 +5756,27 @@ typedef void (*BeforeCallEnteredCallback)(Isolate*);
typedef void (*CallCompletedCallback)(Isolate*);
typedef void (*DeprecatedCallCompletedCallback)();
/**
* PromiseHook with type kInit is called when a new promise is
* created. When a new promise is created as part of the chain in the
* case of Promise.then or in the intermediate promises created by
* Promise.{race, all}/AsyncFunctionAwait, we pass the parent promise
* otherwise we pass undefined.
*
* PromiseHook with type kResolve is called at the beginning of
* resolve or reject function defined by CreateResolvingFunctions.
*
* PromiseHook with type kBefore is called at the beginning of the
* PromiseReactionJob.
*
* PromiseHook with type kAfter is called right at the end of the
* PromiseReactionJob.
*/
enum class PromiseHookType { kInit, kResolve, kBefore, kAfter };
typedef void (*PromiseHook)(PromiseHookType type, Local<Promise> promise,
Local<Value> parent);
// --- Promise Reject Callback ---
enum PromiseRejectEvent {
kPromiseRejectWithNoHandler = 0,
......@@ -6866,6 +6887,12 @@ class V8_EXPORT Isolate {
void RemoveCallCompletedCallback(
DeprecatedCallCompletedCallback callback));
/**
* Experimental: Set the PromiseHook callback for various promise
* lifecycle events.
*/
void SetPromiseHook(PromiseHook hook);
/**
* Set callback to notify about promise reject with no handler, or
* revocation of such a previous notification once the handler is added.
......
......@@ -8260,6 +8260,10 @@ void Isolate::RemoveCallCompletedCallback(
reinterpret_cast<CallCompletedCallback>(callback));
}
void Isolate::SetPromiseHook(PromiseHook hook) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->SetPromiseHook(hook);
}
void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
if (callback == NULL) return;
......
......@@ -1582,9 +1582,8 @@ ExternalReference ExternalReference::is_tail_call_elimination_enabled_address(
return ExternalReference(isolate->is_tail_call_elimination_enabled_address());
}
ExternalReference ExternalReference::is_promisehook_enabled_address(
Isolate* isolate) {
return ExternalReference(isolate->is_promisehook_enabled_address());
ExternalReference ExternalReference::promise_hook_address(Isolate* isolate) {
return ExternalReference(isolate->promise_hook_address());
}
ExternalReference ExternalReference::debug_is_active_address(
......
......@@ -1047,7 +1047,7 @@ class ExternalReference BASE_EMBEDDED {
static ExternalReference invoke_function_callback(Isolate* isolate);
static ExternalReference invoke_accessor_getter_callback(Isolate* isolate);
static ExternalReference is_promisehook_enabled_address(Isolate* isolate);
static ExternalReference promise_hook_address(Isolate* isolate);
V8_EXPORT_PRIVATE static ExternalReference runtime_function_table_address(
Isolate* isolate);
......
......@@ -1890,7 +1890,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
{ // Internal: PromiseInternalConstructor
Handle<JSFunction> function =
SimpleCreateFunction(isolate, factory->empty_string(),
Builtins::kPromiseInternalConstructor, 0, false);
Builtins::kPromiseInternalConstructor, 1, false);
InstallWithIntrinsicDefaultProto(
isolate, function, Context::PROMISE_INTERNAL_CONSTRUCTOR_INDEX);
}
......
......@@ -371,6 +371,12 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
Label do_enqueue(this), fulfill(this), if_cycle(this, Label::kDeferred),
if_rejectpromise(this, Label::kDeferred);
Label cycle_check(this);
GotoUnless(IsPromiseHookEnabled(), &cycle_check);
CallRuntime(Runtime::kPromiseHookResolve, context, promise);
Goto(&cycle_check);
Bind(&cycle_check);
// 6. If SameValue(resolution, promise) is true, then
GotoIf(SameValue(promise, result, context), &if_cycle);
......@@ -591,6 +597,9 @@ TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
{
Node* const instance = AllocateJSPromise(context);
var_result.Bind(instance);
GotoUnless(IsPromiseHookEnabled(), &init);
CallRuntime(Runtime::kPromiseHookInit, context, instance,
UndefinedConstant());
Goto(&init);
}
......@@ -671,9 +680,17 @@ TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
}
TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) {
Node* const context = Parameter(3);
Node* const parent = Parameter(1);
Node* const context = Parameter(4);
Node* const instance = AllocateJSPromise(context);
PromiseInit(instance);
Label out(this);
GotoUnless(IsPromiseHookEnabled(), &out);
CallRuntime(Runtime::kPromiseHookInit, context, instance, parent);
Goto(&out);
Bind(&out);
Return(instance);
}
......@@ -684,6 +701,13 @@ TF_BUILTIN(PromiseCreateAndSet, PromiseBuiltinsAssembler) {
Node* const instance = AllocateJSPromise(context);
PromiseSet(instance, status, result);
Label out(this);
GotoUnless(IsPromiseHookEnabled(), &out);
CallRuntime(Runtime::kPromiseHookInit, context, instance,
UndefinedConstant());
Goto(&out);
Bind(&out);
Return(instance);
}
......@@ -752,7 +776,7 @@ TF_BUILTIN(PromiseThen, PromiseBuiltinsAssembler) {
native_context, Context::INTERNAL_PROMISE_CAPABILITY_INDEX);
Node* const capability =
CallJS(call_callable, context, promise_internal_capability,
UndefinedConstant());
UndefinedConstant(), promise);
var_deferred.Bind(capability);
Goto(&perform_promise_then);
}
......@@ -849,6 +873,7 @@ TF_BUILTIN(PromiseHandleReject, PromiseBuiltinsAssembler) {
}
TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(1);
Node* const value = Parameter(2);
Node* const handler = Parameter(3);
Node* const deferred = Parameter(4);
......@@ -865,13 +890,17 @@ TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) {
Variable var_reason(this, MachineRepresentation::kTagged);
Node* const is_debug_active = IsDebugActive();
Label run_handler(this), if_rejectpromise(this),
debug_push(this, Label::kDeferred), debug_pop(this, Label::kDeferred);
Branch(is_debug_active, &debug_push, &run_handler);
Label run_handler(this), if_rejectpromise(this), promisehook_before(this),
promisehook_after(this), debug_pop(this);
Bind(&debug_push);
GotoUnless(is_debug_active, &promisehook_before);
CallRuntime(Runtime::kDebugPushPromise, context, deferred_promise);
Goto(&promisehook_before);
Bind(&promisehook_before);
{
CallRuntime(Runtime::kDebugPushPromise, context, deferred_promise);
GotoUnless(IsPromiseHookEnabled(), &run_handler);
CallRuntime(Runtime::kPromiseHookBefore, context, promise);
Goto(&run_handler);
}
......@@ -893,14 +922,15 @@ TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) {
Branch(IsUndefined(on_resolve), &if_internalhandler, &if_customhandler);
Bind(&if_internalhandler);
InternalResolvePromise(context, deferred_promise, result, &debug_pop);
InternalResolvePromise(context, deferred_promise, result,
&promisehook_after);
Bind(&if_customhandler);
{
Node* const maybe_exception = CallJS(call_callable, context, on_resolve,
UndefinedConstant(), result);
GotoIfException(maybe_exception, &if_rejectpromise, &var_reason);
Goto(&debug_pop);
Goto(&promisehook_after);
}
}
......@@ -914,6 +944,13 @@ TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) {
Callable promise_handle_reject = CodeFactory::PromiseHandleReject(isolate);
CallStub(promise_handle_reject, context, deferred_promise, on_reject,
var_reason.value());
Goto(&promisehook_after);
}
Bind(&promisehook_after);
{
GotoUnless(IsPromiseHookEnabled(), &debug_pop);
CallRuntime(Runtime::kPromiseHookAfter, context, promise);
Goto(&debug_pop);
}
......
......@@ -566,7 +566,7 @@ namespace internal {
\
/* Promise */ \
TFJ(PromiseConstructor, 1) \
TFJ(PromiseInternalConstructor, 0) \
TFJ(PromiseInternalConstructor, 1) \
TFJ(IsPromise, 1) \
CPP(CreateResolvingFunctions) \
TFJ(PromiseResolveClosure, 1) \
......
......@@ -8247,11 +8247,10 @@ Node* CodeStubAssembler::IsDebugActive() {
}
Node* CodeStubAssembler::IsPromiseHookEnabled() {
Node* const is_promisehook_enabled =
Load(MachineType::Uint8(),
ExternalConstant(
ExternalReference::is_promisehook_enabled_address(isolate())));
return Word32NotEqual(is_promisehook_enabled, Int32Constant(0));
Node* const promise_hook = Load(
MachineType::Pointer(),
ExternalConstant(ExternalReference::promise_hook_address(isolate())));
return WordNotEqual(promise_hook, IntPtrConstant(0));
}
Node* CodeStubAssembler::AllocateJSPromise(Node* context) {
......
......@@ -253,8 +253,8 @@ void ExternalReferenceTable::AddReferences(Isolate* isolate) {
"double_absolute_constant");
Add(ExternalReference::address_of_double_neg_constant().address(),
"double_negate_constant");
Add(ExternalReference::is_promisehook_enabled_address(isolate).address(),
"Isolate::is_promisehook_enabled_address()");
Add(ExternalReference::promise_hook_address(isolate).address(),
"Isolate::promise_hook_address()");
// Debug addresses
Add(ExternalReference::debug_after_break_target_address(isolate).address(),
......
......@@ -2148,11 +2148,11 @@ Isolate::Isolate(bool enable_serializer)
// be fixed once the default isolate cleanup is done.
random_number_generator_(NULL),
rail_mode_(PERFORMANCE_ANIMATION),
promise_hook_(NULL),
load_start_time_ms_(0),
serializer_enabled_(enable_serializer),
has_fatal_error_(false),
initialized_from_snapshot_(false),
is_promisehook_enabled_(false),
is_tail_call_elimination_enabled_(true),
is_isolate_in_background_(false),
cpu_profiler_(NULL),
......@@ -3170,9 +3170,14 @@ void Isolate::FireCallCompletedCallback() {
}
}
void Isolate::EnablePromiseHook() { is_promisehook_enabled_ = true; }
void Isolate::SetPromiseHook(PromiseHook hook) { promise_hook_ = hook; }
void Isolate::DisablePromiseHook() { is_promisehook_enabled_ = false; }
void Isolate::RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent) {
if (promise_hook_ == nullptr) return;
promise_hook_(type, v8::Utils::PromiseToLocal(promise),
v8::Utils::ToLocal(parent));
}
void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
promise_reject_callback_ = callback;
......
......@@ -1125,12 +1125,12 @@ class Isolate {
int GetNextUniqueSharedFunctionInfoId() { return next_unique_sfi_id_++; }
#endif
Address is_promisehook_enabled_address() {
return reinterpret_cast<Address>(&is_promisehook_enabled_);
Address promise_hook_address() {
return reinterpret_cast<Address>(&promise_hook_);
}
bool IsPromiseHookEnabled() { return is_promisehook_enabled_; }
void EnablePromiseHook();
void DisablePromiseHook();
void SetPromiseHook(PromiseHook hook);
void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent);
// Support for dynamically disabling tail call elimination.
Address is_tail_call_elimination_enabled_address() {
......@@ -1366,6 +1366,7 @@ class Isolate {
AccessCompilerData* access_compiler_data_;
base::RandomNumberGenerator* random_number_generator_;
base::AtomicValue<RAILMode> rail_mode_;
PromiseHook promise_hook_;
base::Mutex rail_mutex_;
double load_start_time_ms_;
......@@ -1378,9 +1379,6 @@ class Isolate {
// True if this isolate was initialized from a snapshot.
bool initialized_from_snapshot_;
// True if PromiseHook feature is enabled.
bool is_promisehook_enabled_;
// True if ES2015 tail call elimination feature is enabled.
bool is_tail_call_elimination_enabled_;
......
......@@ -82,7 +82,7 @@ function AsyncFunctionAwait(generator, awaited, outerPromise) {
}
// Just forwarding the exception, so no debugEvent for throwawayCapability.
var throwawayCapability = CreateInternalPromiseCapability();
var throwawayCapability = CreateInternalPromiseCapability(promise);
// The Promise will be thrown away and not handled, but it shouldn't trigger
// unhandled reject events as its work is done
......
......@@ -76,7 +76,7 @@ SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true);
// This is used by utils and v8-extras.
function PromiseCreate() {
return %promise_internal_constructor();
return %promise_internal_constructor(UNDEFINED);
}
// Only used by async-await.js
......@@ -92,9 +92,9 @@ function DoRejectPromise(promise, reason) {
// The resultCapability.promise is only ever fulfilled internally,
// so we don't need the closures to protect against accidentally
// calling them multiple times.
function CreateInternalPromiseCapability() {
function CreateInternalPromiseCapability(parent) {
return {
promise: %promise_internal_constructor(),
promise: %promise_internal_constructor(parent),
resolve: UNDEFINED,
reject: UNDEFINED
};
......@@ -105,7 +105,7 @@ function CreateInternalPromiseCapability() {
function NewPromiseCapability(C, debugEvent) {
if (C === GlobalPromise) {
// Optimized case, avoid extra closure.
var promise = %promise_internal_constructor();
var promise = %promise_internal_constructor(UNDEFINED);
// TODO(gsathya): Remove container for callbacks when this is
// moved to CPP/TF.
var callbacks = %create_resolving_functions(promise, debugEvent);
......@@ -168,7 +168,7 @@ function PromiseResolve(x) {
// Avoid creating resolving functions.
if (this === GlobalPromise) {
var promise = %promise_internal_constructor();
var promise = %promise_internal_constructor(UNDEFINED);
%promise_resolve(promise, x);
return promise;
}
......
......@@ -11,8 +11,8 @@
namespace v8 {
namespace internal {
JSObject* PromiseUtils::GetPromise(Handle<Context> context) {
return JSObject::cast(context->get(kPromiseSlot));
JSPromise* PromiseUtils::GetPromise(Handle<Context> context) {
return JSPromise::cast(context->get(kPromiseSlot));
}
Object* PromiseUtils::GetDebugEvent(Handle<Context> context) {
......
......@@ -29,7 +29,7 @@ class PromiseUtils : public AllStatic {
// These get and set the slots on the PromiseResolvingContext, which
// is used by the resolve/reject promise callbacks.
static JSObject* GetPromise(Handle<Context> context);
static JSPromise* GetPromise(Handle<Context> context);
static Object* GetDebugEvent(Handle<Context> context);
static bool HasAlreadyVisited(Handle<Context> context);
static void SetAlreadyVisited(Handle<Context> context);
......
......@@ -15,6 +15,9 @@ namespace {
void PromiseRejectEvent(Isolate* isolate, Handle<JSPromise> promise,
Handle<Object> rejected_promise, Handle<Object> value,
bool debug_event) {
isolate->RunPromiseHook(PromiseHookType::kResolve, promise,
isolate->factory()->undefined_value());
if (isolate->debug()->is_active() && debug_event) {
isolate->debug()->OnPromiseReject(rejected_promise, value);
}
......@@ -284,5 +287,41 @@ RUNTIME_FUNCTION(Runtime_PromiseMarkAsHandled) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseHookInit) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, parent, 1);
isolate->RunPromiseHook(PromiseHookType::kInit, promise, parent);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseHookResolve) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
isolate->RunPromiseHook(PromiseHookType::kResolve, promise,
isolate->factory()->undefined_value());
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseHookBefore) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
isolate->RunPromiseHook(PromiseHookType::kBefore, promise,
isolate->factory()->undefined_value());
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseHookAfter) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
isolate->RunPromiseHook(PromiseHookType::kAfter, promise,
isolate->factory()->undefined_value());
return isolate->heap()->undefined_value();
}
} // namespace internal
} // namespace v8
......@@ -293,7 +293,7 @@ namespace internal {
F(CreateListFromArrayLike, 1, 1) \
F(CreateResolvingFunctions, 1, 1) \
F(EnqueueMicrotask, 1, 1) \
F(EnqueuePromiseReactionJob, 4, 1) \
F(EnqueuePromiseReactionJob, 5, 1) \
F(EnqueuePromiseResolveThenableJob, 3, 1) \
F(GetAndResetRuntimeCallStats, -1 /* <= 2 */, 1) \
F(ExportExperimentalFromRuntime, 1, 1) \
......@@ -309,6 +309,10 @@ namespace internal {
F(PromiseDeferred, 1, 1) \
F(PromiseReject, 3, 1) \
F(PromiseFulfill, 3, 1) \
F(PromiseHookInit, 2, 1) \
F(PromiseHookResolve, 1, 1) \
F(PromiseHookBefore, 1, 1) \
F(PromiseHookAfter, 1, 1) \
F(PromiseMarkAsHandled, 1, 1) \
F(PromiseRejectEventFromStack, 2, 1) \
F(PromiseRejectReactions, 1, 1) \
......
......@@ -1392,8 +1392,7 @@ class WasmInstanceBuilder {
// Helper routines to print out errors with imports.
void ReportLinkError(const char* error, uint32_t index,
Handle<String> module_name,
Handle<String> import_name) {
Handle<String> module_name, Handle<String> import_name) {
thrower_->LinkError(
"Import #%d module=\"%.*s\" function=\"%.*s\" error: %s", index,
module_name->length(), module_name->ToCString().get(),
......@@ -1431,7 +1430,7 @@ class WasmInstanceBuilder {
// Look up the value in the module.
if (!module->IsJSReceiver()) {
return ReportTypeError("module is not an object or function", index,
module_name);
module_name);
}
result = Object::GetPropertyOrElement(module, import_name);
......@@ -1522,17 +1521,15 @@ class WasmInstanceBuilder {
WasmImport& import = module_->import_table[index];
Handle<String> module_name;
MaybeHandle<String> maybe_module_name =
ExtractStringFromModuleBytes(isolate_, compiled_module_,
import.module_name_offset,
import.module_name_length);
MaybeHandle<String> maybe_module_name = ExtractStringFromModuleBytes(
isolate_, compiled_module_, import.module_name_offset,
import.module_name_length);
if (!maybe_module_name.ToHandle(&module_name)) return -1;
Handle<String> import_name;
MaybeHandle<String> maybe_import_name =
ExtractStringFromModuleBytes(isolate_, compiled_module_,
import.field_name_offset,
import.field_name_length);
MaybeHandle<String> maybe_import_name = ExtractStringFromModuleBytes(
isolate_, compiled_module_, import.field_name_offset,
import.field_name_length);
if (!maybe_import_name.ToHandle(&import_name)) return -1;
MaybeHandle<Object> result =
......@@ -1555,8 +1552,8 @@ class WasmInstanceBuilder {
module_->origin);
if (import_wrapper.is_null()) {
ReportLinkError(
"imported function does not match the expected type",
index, module_name, import_name);
"imported function does not match the expected type", index,
module_name, import_name);
return -1;
}
code_table->set(num_imported_functions, *import_wrapper);
......@@ -1630,8 +1627,8 @@ class WasmInstanceBuilder {
// Global imports are converted to numbers and written into the
// {globals_} array buffer.
if (!value->IsNumber()) {
ReportLinkError("global import must be a number",
index, module_name, import_name);
ReportLinkError("global import must be a number", index,
module_name, import_name);
return -1;
}
WriteGlobalValue(module_->globals[import.index], value);
......
This diff is collapsed.
......@@ -1792,6 +1792,9 @@ TEST(CodeStubAssemblerGraphsCorrectness) {
v8_isolate->Dispose();
}
void CustomPromiseHook(v8::PromiseHookType type, v8::Local<v8::Promise> promise,
v8::Local<v8::Value> parentPromise) {}
TEST(IsPromiseHookEnabled) {
Isolate* isolate(CcTest::InitIsolateOnce());
......@@ -1805,16 +1808,15 @@ TEST(IsPromiseHookEnabled) {
CHECK(!code.is_null());
FunctionTester ft(code, kNumParams);
CHECK_EQ(false, isolate->IsPromiseHookEnabled());
Handle<Object> result =
ft.Call(isolate->factory()->undefined_value()).ToHandleChecked();
CHECK_EQ(isolate->heap()->false_value(), *result);
isolate->EnablePromiseHook();
isolate->SetPromiseHook(CustomPromiseHook);
result = ft.Call(isolate->factory()->undefined_value()).ToHandleChecked();
CHECK_EQ(isolate->heap()->true_value(), *result);
isolate->DisablePromiseHook();
isolate->SetPromiseHook(nullptr);
result = ft.Call(isolate->factory()->undefined_value()).ToHandleChecked();
CHECK_EQ(isolate->heap()->false_value(), *result);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment