Introduce API to temporarily interrupt long running JavaScript code.
authorvegorov@chromium.org <vegorov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 18 Dec 2013 18:38:35 +0000 (18:38 +0000)
committervegorov@chromium.org <vegorov@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 18 Dec 2013 18:38:35 +0000 (18:38 +0000)
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
src/api.cc
src/execution.cc
src/execution.h
src/hydrogen-instructions.h
src/hydrogen-sce.cc
test/cctest/test-api.cc

index 99827fe..d3fc0fe 100644 (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&);
index 9a68f63..9c22ceb 100644 (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);
index c0e9a64..634c83a 100644 (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");
index eda416c..3e62d87 100644 (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
index fde3abc..00b18bd 100644 (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:
index a6995f6..70b0a0c 100644 (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;
           }
index 9ea8b0d..953be8d 100644 (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;