From 64d2dfa2324ad936364673b75d6e68e7d16df3d0 Mon Sep 17 00:00:00 2001 From: "vegorov@chromium.org" Date: Wed, 18 Dec 2013 18:38:35 +0000 Subject: [PATCH] Introduce API to temporarily interrupt long running JavaScript code. It is different from termination API as interrupted JavaScript will continue to execute normally when registered InterruptCallback returns. /** * Request V8 to interrupt long running JavaScript code and invoke * the given |callback| passing the given |data| to it. After |callback| * returns control will be returned to the JavaScript code. * At any given moment V8 can remember only a single callback for the very * last interrupt request. * Can be called from another thread without acquiring a |Locker|. * Registered |callback| must not reenter interrupted Isolate. */ void RequestInterrupt(InterruptCallback callback, void* data); /** * Clear interrupt request created by |RequestInterrupt|. * Can be called from another thread without acquiring a |Locker|. */ void ClearInterrupt(); Fix Hydrogen SCE pass to avoid eliminating stack guards too aggressively. Only normal JavaScript functions are guaranteed to have stack guard in the prologue. If function is a builtin or has a custom call IC it will lack one. BUG= R=danno@chromium.org, dcarney@chromium.org Review URL: https://codereview.chromium.org/102063004 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18363 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 19 ++++ src/api.cc | 11 ++ src/execution.cc | 49 +++++++++ src/execution.h | 11 +- src/hydrogen-instructions.h | 20 +++- src/hydrogen-sce.cc | 2 +- test/cctest/test-api.cc | 256 +++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 362 insertions(+), 6 deletions(-) diff --git a/include/v8.h b/include/v8.h index 99827fe..d3fc0fe 100644 --- a/include/v8.h +++ b/include/v8.h @@ -3918,6 +3918,8 @@ enum GCCallbackFlags { typedef void (*GCPrologueCallback)(GCType type, GCCallbackFlags flags); typedef void (*GCEpilogueCallback)(GCType type, GCCallbackFlags flags); +typedef void (*InterruptCallback)(Isolate* isolate, void* data); + /** * Collection of V8 heap information. @@ -4174,6 +4176,23 @@ class V8_EXPORT Isolate { */ void RemoveGCEpilogueCallback(GCEpilogueCallback callback); + /** + * Request V8 to interrupt long running JavaScript code and invoke + * the given |callback| passing the given |data| to it. After |callback| + * returns control will be returned to the JavaScript code. + * At any given moment V8 can remember only a single callback for the very + * last interrupt request. + * Can be called from another thread without acquiring a |Locker|. + * Registered |callback| must not reenter interrupted Isolate. + */ + void RequestInterrupt(InterruptCallback callback, void* data); + + /** + * Clear interrupt request created by |RequestInterrupt|. + * Can be called from another thread without acquiring a |Locker|. + */ + void ClearInterrupt(); + private: Isolate(); Isolate(const Isolate&); diff --git a/src/api.cc b/src/api.cc index 9a68f63..9c22ceb 100644 --- a/src/api.cc +++ b/src/api.cc @@ -6500,6 +6500,17 @@ void V8::CancelTerminateExecution(Isolate* isolate) { } +void Isolate::RequestInterrupt(InterruptCallback callback, void* data) { + reinterpret_cast(this)->stack_guard()->RequestInterrupt( + callback, data); +} + + +void Isolate::ClearInterrupt() { + reinterpret_cast(this)->stack_guard()->ClearInterrupt(); +} + + Isolate* Isolate::GetCurrent() { i::Isolate* isolate = i::Isolate::UncheckedCurrent(); return reinterpret_cast(isolate); diff --git a/src/execution.cc b/src/execution.cc index c0e9a64..634c83a 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -540,6 +540,48 @@ void StackGuard::Continue(InterruptFlag after_what) { } +void StackGuard::RequestInterrupt(InterruptCallback callback, void* data) { + ExecutionAccess access(isolate_); + thread_local_.interrupt_flags_ |= API_INTERRUPT; + thread_local_.interrupt_callback_ = callback; + thread_local_.interrupt_callback_data_ = data; + set_interrupt_limits(access); +} + + +void StackGuard::ClearInterrupt() { + thread_local_.interrupt_callback_ = 0; + thread_local_.interrupt_callback_data_ = 0; + Continue(API_INTERRUPT); +} + + +bool StackGuard::IsAPIInterrupt() { + ExecutionAccess access(isolate_); + return thread_local_.interrupt_flags_ & API_INTERRUPT; +} + + +void StackGuard::InvokeInterruptCallback() { + InterruptCallback callback = 0; + void* data = 0; + + { + ExecutionAccess access(isolate_); + callback = thread_local_.interrupt_callback_; + data = thread_local_.interrupt_callback_data_; + thread_local_.interrupt_callback_ = NULL; + thread_local_.interrupt_callback_data_ = NULL; + } + + if (callback != NULL) { + VMState state(isolate_); + HandleScope handle_scope(isolate_); + callback(reinterpret_cast(isolate_), data); + } +} + + char* StackGuard::ArchiveStackGuard(char* to) { ExecutionAccess access(isolate_); OS::MemCopy(to, reinterpret_cast(&thread_local_), sizeof(ThreadLocal)); @@ -581,6 +623,7 @@ void StackGuard::ThreadLocal::Clear() { nesting_ = 0; postpone_interrupts_nesting_ = 0; interrupt_flags_ = 0; + interrupt_callback_ = 0; } @@ -601,6 +644,7 @@ bool StackGuard::ThreadLocal::Initialize(Isolate* isolate) { nesting_ = 0; postpone_interrupts_nesting_ = 0; interrupt_flags_ = 0; + interrupt_callback_ = 0; return should_set_stack_limits; } @@ -936,6 +980,11 @@ MaybeObject* Execution::HandleStackGuardInterrupt(Isolate* isolate) { return isolate->heap()->undefined_value(); } + if (stack_guard->IsAPIInterrupt()) { + stack_guard->InvokeInterruptCallback(); + stack_guard->Continue(API_INTERRUPT); + } + if (stack_guard->IsGCRequest()) { isolate->heap()->CollectAllGarbage(Heap::kNoGCFlags, "StackGuard GC request"); diff --git a/src/execution.h b/src/execution.h index eda416c..3e62d87 100644 --- a/src/execution.h +++ b/src/execution.h @@ -43,7 +43,8 @@ enum InterruptFlag { TERMINATE = 1 << 4, GC_REQUEST = 1 << 5, FULL_DEOPT = 1 << 6, - INSTALL_CODE = 1 << 7 + INSTALL_CODE = 1 << 7, + API_INTERRUPT = 1 << 8 }; @@ -222,6 +223,11 @@ class StackGuard { void FullDeopt(); void Continue(InterruptFlag after_what); + void RequestInterrupt(InterruptCallback callback, void* data); + void ClearInterrupt(); + bool IsAPIInterrupt(); + void InvokeInterruptCallback(); + // This provides an asynchronous read of the stack limits for the current // thread. There are no locks protecting this, but it is assumed that you // have the global V8 lock if you are using multiple V8 threads. @@ -307,6 +313,9 @@ class StackGuard { int nesting_; int postpone_interrupts_nesting_; int interrupt_flags_; + + InterruptCallback interrupt_callback_; + void* interrupt_callback_data_; }; // TODO(isolates): Technically this could be calculated directly from a diff --git a/src/hydrogen-instructions.h b/src/hydrogen-instructions.h index fde3abc..00b18bd 100644 --- a/src/hydrogen-instructions.h +++ b/src/hydrogen-instructions.h @@ -1245,7 +1245,7 @@ class HInstruction : public HValue { virtual void Verify() V8_OVERRIDE; #endif - virtual bool IsCall() { return false; } + virtual bool HasStackCheck() { return false; } DECLARE_ABSTRACT_INSTRUCTION(Instruction) @@ -2243,8 +2243,6 @@ class HCall : public HTemplateInstruction { return -argument_count(); } - virtual bool IsCall() V8_FINAL V8_OVERRIDE { return true; } - private: int argument_count_; }; @@ -2316,6 +2314,12 @@ class HInvokeFunction V8_FINAL : public HBinaryCall { Handle known_function() { return known_function_; } int formal_parameter_count() const { return formal_parameter_count_; } + virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE { + return !known_function().is_null() && + (known_function()->code()->kind() == Code::FUNCTION || + known_function()->code()->kind() == Code::OPTIMIZED_FUNCTION); + } + DECLARE_CONCRETE_INSTRUCTION(InvokeFunction) private: @@ -2348,6 +2352,11 @@ class HCallConstantFunction V8_FINAL : public HCall<0> { return Representation::None(); } + virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE { + return (function()->code()->kind() == Code::FUNCTION || + function()->code()->kind() == Code::OPTIMIZED_FUNCTION); + } + DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction) private: @@ -2465,6 +2474,11 @@ class HCallKnownGlobal V8_FINAL : public HCall<0> { return Representation::None(); } + virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE { + return (target()->code()->kind() == Code::FUNCTION || + target()->code()->kind() == Code::OPTIMIZED_FUNCTION); + } + DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal) private: diff --git a/src/hydrogen-sce.cc b/src/hydrogen-sce.cc index a6995f6..70b0a0c 100644 --- a/src/hydrogen-sce.cc +++ b/src/hydrogen-sce.cc @@ -43,7 +43,7 @@ void HStackCheckEliminationPhase::Run() { HBasicBlock* dominator = back_edge; while (true) { for (HInstructionIterator it(dominator); !it.Done(); it.Advance()) { - if (it.Current()->IsCall()) { + if (it.Current()->HasStackCheck()) { block->loop_information()->stack_check()->Eliminate(); break; } diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 9ea8b0d..953be8d 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -20736,6 +20736,9 @@ THREADED_TEST(SemaphoreInterruption) { } +#endif // V8_OS_POSIX + + static bool NamedAccessAlwaysBlocked(Local global, Local name, v8::AccessType type, @@ -21022,7 +21025,258 @@ THREADED_TEST(CrankshaftInterceptorFieldWrite) { } -#endif // V8_OS_POSIX +class RequestInterruptTestBase { + public: + RequestInterruptTestBase() + : env_(), + isolate_(env_->GetIsolate()), + sem_(0), + warmup_(20000), + should_continue_(true) { + } + + virtual ~RequestInterruptTestBase() { } + + virtual void TestBody() = 0; + + void RunTest() { + i::FLAG_print_opt_code = true; + i::FLAG_code_comments = true; + i::FLAG_print_code_stubs = true; + InterruptThread i_thread(this); + i_thread.Start(); + + v8::HandleScope handle_scope(isolate_); + + TestBody(); + + isolate_->ClearInterrupt(); + } + + void WakeUpInterruptor() { + sem_.Signal(); + } + + bool should_continue() const { return should_continue_; } + + bool ShouldContinue() { + if (warmup_ > 0) { + if (--warmup_ == 0) { + WakeUpInterruptor(); + } + } + + return should_continue_; + } + + protected: + static void ShouldContinueCallback( + const v8::FunctionCallbackInfo& info) { + RequestInterruptTestBase* test = + reinterpret_cast( + info.Data().As()->Value()); + info.GetReturnValue().Set(test->ShouldContinue()); + } + + class InterruptThread : public i::Thread { + public: + explicit InterruptThread(RequestInterruptTestBase* test) + : Thread("RequestInterruptTest"), test_(test) {} + + virtual void Run() { + test_->sem_.Wait(); + test_->isolate_->RequestInterrupt(&OnInterrupt, test_); + } + + static void OnInterrupt(v8::Isolate* isolate, void* data) { + reinterpret_cast(data)-> + should_continue_ = false; + } + + private: + RequestInterruptTestBase* test_; + }; + + LocalContext env_; + v8::Isolate* isolate_; + i::Semaphore sem_; + int warmup_; + bool should_continue_; +}; + + +class RequestInterruptTestWithFunctionCall : public RequestInterruptTestBase { + public: + virtual void TestBody() { + Local func = Function::New( + isolate_, ShouldContinueCallback, v8::External::New(isolate_, this)); + env_->Global()->Set(v8_str("ShouldContinue"), func); + + CompileRun("while (ShouldContinue()) { }"); + } +}; + + +class RequestInterruptTestWithMethodCall : public RequestInterruptTestBase { + public: + virtual void TestBody() { + v8::Local t = v8::FunctionTemplate::New(isolate_); + v8::Local proto = t->PrototypeTemplate(); + proto->Set(v8_str("shouldContinue"), Function::New( + isolate_, ShouldContinueCallback, v8::External::New(isolate_, this))); + env_->Global()->Set(v8_str("Klass"), t->GetFunction()); + + CompileRun("var obj = new Klass; while (obj.shouldContinue()) { }"); + } +}; + + +class RequestInterruptTestWithAccessor : public RequestInterruptTestBase { + public: + virtual void TestBody() { + v8::Local t = v8::FunctionTemplate::New(isolate_); + v8::Local proto = t->PrototypeTemplate(); + proto->SetAccessorProperty(v8_str("shouldContinue"), FunctionTemplate::New( + isolate_, ShouldContinueCallback, v8::External::New(isolate_, this))); + env_->Global()->Set(v8_str("Klass"), t->GetFunction()); + + CompileRun("var obj = new Klass; while (obj.shouldContinue) { }"); + } +}; + + +class RequestInterruptTestWithNativeAccessor : public RequestInterruptTestBase { + public: + virtual void TestBody() { + v8::Local t = v8::FunctionTemplate::New(isolate_); + v8::Local proto = t->PrototypeTemplate(); + proto->SetNativeDataProperty(v8_str("shouldContinue"), + &ShouldContinueNativeGetter, + NULL, + v8::External::New(isolate_, this)); + env_->Global()->Set(v8_str("Klass"), t->GetFunction()); + + CompileRun("var obj = new Klass; while (obj.shouldContinue) { }"); + } + + private: + static void ShouldContinueNativeGetter( + Local property, + const v8::PropertyCallbackInfo& info) { + RequestInterruptTestBase* test = + reinterpret_cast( + info.Data().As()->Value()); + info.GetReturnValue().Set(test->ShouldContinue()); + } +}; + + +class RequestInterruptTestWithMethodCallAndInterceptor + : public RequestInterruptTestBase { + public: + virtual void TestBody() { + v8::Local t = v8::FunctionTemplate::New(isolate_); + v8::Local proto = t->PrototypeTemplate(); + proto->Set(v8_str("shouldContinue"), Function::New( + isolate_, ShouldContinueCallback, v8::External::New(isolate_, this))); + v8::Local instance_template = t->InstanceTemplate(); + instance_template->SetNamedPropertyHandler(EmptyInterceptor); + + env_->Global()->Set(v8_str("Klass"), t->GetFunction()); + + CompileRun("var obj = new Klass; while (obj.shouldContinue()) { }"); + } + + private: + static void EmptyInterceptor( + Local property, + const v8::PropertyCallbackInfo& info) { + } +}; + + +class RequestInterruptTestWithMathAbs : public RequestInterruptTestBase { + public: + virtual void TestBody() { + env_->Global()->Set(v8_str("WakeUpInterruptor"), Function::New( + isolate_, + WakeUpInterruptorCallback, + v8::External::New(isolate_, this))); + + env_->Global()->Set(v8_str("ShouldContinue"), Function::New( + isolate_, + ShouldContinueCallback, + v8::External::New(isolate_, this))); + + i::FLAG_allow_natives_syntax = true; + CompileRun("function loopish(o) {" + " var pre = 10;" + " while (o.abs(1) > 0) {" + " if (o.abs(1) >= 0 && !ShouldContinue()) break;" + " if (pre > 0) {" + " if (--pre === 0) WakeUpInterruptor(o === Math);" + " }" + " }" + "}" + "var i = 50;" + "var obj = {abs: function () { return i-- }, x: null};" + "delete obj.x;" + "loopish(obj);" + "%OptimizeFunctionOnNextCall(loopish);" + "loopish(Math);"); + + i::FLAG_allow_natives_syntax = false; + } + + private: + static void WakeUpInterruptorCallback( + const v8::FunctionCallbackInfo& info) { + if (!info[0]->BooleanValue()) return; + + RequestInterruptTestBase* test = + reinterpret_cast( + info.Data().As()->Value()); + test->WakeUpInterruptor(); + } + + static void ShouldContinueCallback( + const v8::FunctionCallbackInfo& info) { + RequestInterruptTestBase* test = + reinterpret_cast( + info.Data().As()->Value()); + info.GetReturnValue().Set(test->should_continue()); + } +}; + + +THREADED_TEST(RequestInterruptTestWithFunctionCall) { + RequestInterruptTestWithFunctionCall().RunTest(); +} + + +THREADED_TEST(RequestInterruptTestWithMethodCall) { + RequestInterruptTestWithMethodCall().RunTest(); +} + + +THREADED_TEST(RequestInterruptTestWithAccessor) { + RequestInterruptTestWithAccessor().RunTest(); +} + + +THREADED_TEST(RequestInterruptTestWithNativeAccessor) { + RequestInterruptTestWithNativeAccessor().RunTest(); +} + + +THREADED_TEST(RequestInterruptTestWithMethodCallAndInterceptor) { + RequestInterruptTestWithMethodCallAndInterceptor().RunTest(); +} + + +THREADED_TEST(RequestInterruptTestWithMathAbs) { + RequestInterruptTestWithMathAbs().RunTest(); +} static Local function_new_expected_env; -- 2.7.4