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
This commit is contained in:
vegorov@chromium.org 2013-12-18 18:38:35 +00:00
parent e3752b63c0
commit 64d2dfa232
7 changed files with 362 additions and 6 deletions

View File

@ -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&);

View File

@ -6500,6 +6500,17 @@ void V8::CancelTerminateExecution(Isolate* isolate) {
}
void Isolate::RequestInterrupt(InterruptCallback callback, void* data) {
reinterpret_cast<i::Isolate*>(this)->stack_guard()->RequestInterrupt(
callback, data);
}
void Isolate::ClearInterrupt() {
reinterpret_cast<i::Isolate*>(this)->stack_guard()->ClearInterrupt();
}
Isolate* Isolate::GetCurrent() {
i::Isolate* isolate = i::Isolate::UncheckedCurrent();
return reinterpret_cast<Isolate*>(isolate);

View File

@ -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<EXTERNAL> state(isolate_);
HandleScope handle_scope(isolate_);
callback(reinterpret_cast<v8::Isolate*>(isolate_), data);
}
}
char* StackGuard::ArchiveStackGuard(char* to) {
ExecutionAccess access(isolate_);
OS::MemCopy(to, reinterpret_cast<char*>(&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");

View File

@ -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

View File

@ -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<V> {
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<JSFunction> 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:

View File

@ -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;
}

View File

@ -20736,6 +20736,9 @@ THREADED_TEST(SemaphoreInterruption) {
}
#endif // V8_OS_POSIX
static bool NamedAccessAlwaysBlocked(Local<v8::Object> global,
Local<Value> 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<Value>& info) {
RequestInterruptTestBase* test =
reinterpret_cast<RequestInterruptTestBase*>(
info.Data().As<v8::External>()->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<RequestInterruptTestBase*>(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<Function> 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<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
v8::Local<v8::Template> 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<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
v8::Local<v8::Template> 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<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
v8::Local<v8::Template> 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<String> property,
const v8::PropertyCallbackInfo<v8::Value>& info) {
RequestInterruptTestBase* test =
reinterpret_cast<RequestInterruptTestBase*>(
info.Data().As<v8::External>()->Value());
info.GetReturnValue().Set(test->ShouldContinue());
}
};
class RequestInterruptTestWithMethodCallAndInterceptor
: public RequestInterruptTestBase {
public:
virtual void TestBody() {
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
v8::Local<v8::Template> proto = t->PrototypeTemplate();
proto->Set(v8_str("shouldContinue"), Function::New(
isolate_, ShouldContinueCallback, v8::External::New(isolate_, this)));
v8::Local<v8::ObjectTemplate> 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<String> property,
const v8::PropertyCallbackInfo<v8::Value>& 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<Value>& info) {
if (!info[0]->BooleanValue()) return;
RequestInterruptTestBase* test =
reinterpret_cast<RequestInterruptTestBase*>(
info.Data().As<v8::External>()->Value());
test->WakeUpInterruptor();
}
static void ShouldContinueCallback(
const v8::FunctionCallbackInfo<Value>& info) {
RequestInterruptTestBase* test =
reinterpret_cast<RequestInterruptTestBase*>(
info.Data().As<v8::External>()->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<Value> function_new_expected_env;