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
This commit is contained in:
vegorov@chromium.org 2013-12-19 16:45:58 +00:00
parent 0fea01de4e
commit bc77d4d60a
7 changed files with 371 additions and 9 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_;
};
@ -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<JSFunction> 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<JSFunction> 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<JSFunction> 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<JSFunction> 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<JSFunction> 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<JSFunction> target_;
int formal_parameter_count_;
bool has_stack_check_;
};

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,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<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());
}
};
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<Value> function_new_expected_env;