From bc77d4d60aec84ac3c2e6d9e3157b520cf18eb54 Mon Sep 17 00:00:00 2001 From: "vegorov@chromium.org" Date: Thu, 19 Dec 2013 16:45:58 +0000 Subject: [PATCH] Reland r18363. 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. Changes from r18363: - includes r18364 to fix compilation errors: removes debugging only code; - makes interrupiton related tests not threaded, because they rely on having exclusive access to the V8 instance and the fact that they can only interrupt themselves, if they are interrupted from the outside they break; - changes HasStackCheck predicate used during SCE pass to avoid handles dereference to make SCE compatible with parallel recompilation. R=dcarney@chromium.org Review URL: https://codereview.chromium.org/104823008 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18375 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 | 35 ++++-- src/hydrogen-sce.cc | 2 +- test/cctest/test-api.cc | 253 +++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 371 insertions(+), 9 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..1e270ab 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_; }; @@ -2300,6 +2298,9 @@ class HInvokeFunction V8_FINAL : public HBinaryCall { known_function_(known_function) { formal_parameter_count_ = known_function.is_null() ? 0 : known_function->shared()->formal_parameter_count(); + has_stack_check_ = !known_function.is_null() && + (known_function->code()->kind() == Code::FUNCTION || + known_function->code()->kind() == Code::OPTIMIZED_FUNCTION); } static HInvokeFunction* New(Zone* zone, @@ -2316,15 +2317,21 @@ 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 has_stack_check_; + } + DECLARE_CONCRETE_INSTRUCTION(InvokeFunction) private: HInvokeFunction(HValue* context, HValue* function, int argument_count) - : HBinaryCall(context, function, argument_count) { + : HBinaryCall(context, function, argument_count), + has_stack_check_(false) { } Handle known_function_; int formal_parameter_count_; + bool has_stack_check_; }; @@ -2348,16 +2355,24 @@ class HCallConstantFunction V8_FINAL : public HCall<0> { return Representation::None(); } + virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE { + return has_stack_check_; + } + DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction) private: HCallConstantFunction(Handle function, int argument_count) : HCall<0>(argument_count), function_(function), - formal_parameter_count_(function->shared()->formal_parameter_count()) {} + formal_parameter_count_(function->shared()->formal_parameter_count()), + has_stack_check_( + function->code()->kind() == Code::FUNCTION || + function->code()->kind() == Code::OPTIMIZED_FUNCTION) {} Handle function_; int formal_parameter_count_; + bool has_stack_check_; }; @@ -2465,16 +2480,24 @@ class HCallKnownGlobal V8_FINAL : public HCall<0> { return Representation::None(); } + virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE { + return has_stack_check_; + } + DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal) private: HCallKnownGlobal(Handle target, int argument_count) : HCall<0>(argument_count), target_(target), - formal_parameter_count_(target->shared()->formal_parameter_count()) { } + formal_parameter_count_(target->shared()->formal_parameter_count()), + has_stack_check_( + target->code()->kind() == Code::FUNCTION || + target->code()->kind() == Code::OPTIMIZED_FUNCTION) {} Handle target_; int formal_parameter_count_; + bool has_stack_check_; }; 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..644f00d 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,255 @@ 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() { + 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()); + } +}; + + +TEST(RequestInterruptTestWithFunctionCall) { + RequestInterruptTestWithFunctionCall().RunTest(); +} + + +TEST(RequestInterruptTestWithMethodCall) { + RequestInterruptTestWithMethodCall().RunTest(); +} + + +TEST(RequestInterruptTestWithAccessor) { + RequestInterruptTestWithAccessor().RunTest(); +} + + +TEST(RequestInterruptTestWithNativeAccessor) { + RequestInterruptTestWithNativeAccessor().RunTest(); +} + + +TEST(RequestInterruptTestWithMethodCallAndInterceptor) { + RequestInterruptTestWithMethodCallAndInterceptor().RunTest(); +} + + +TEST(RequestInterruptTestWithMathAbs) { + RequestInterruptTestWithMathAbs().RunTest(); +} static Local function_new_expected_env; -- 2.7.4