Revision: 18363
Author:   [email protected]
Date:     Wed Dec 18 18:38:35 2013 UTC
Log: 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=
[email protected], [email protected]

Review URL: https://codereview.chromium.org/102063004
http://code.google.com/p/v8/source/detail?r=18363

Modified:
 /branches/bleeding_edge/include/v8.h
 /branches/bleeding_edge/src/api.cc
 /branches/bleeding_edge/src/execution.cc
 /branches/bleeding_edge/src/execution.h
 /branches/bleeding_edge/src/hydrogen-instructions.h
 /branches/bleeding_edge/src/hydrogen-sce.cc
 /branches/bleeding_edge/test/cctest/test-api.cc

=======================================
--- /branches/bleeding_edge/include/v8.h        Wed Dec 18 08:09:37 2013 UTC
+++ /branches/bleeding_edge/include/v8.h        Wed Dec 18 18:38:35 2013 UTC
@@ -3918,6 +3918,8 @@
 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 @@
    */
   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&);
=======================================
--- /branches/bleeding_edge/src/api.cc  Wed Dec 18 10:31:42 2013 UTC
+++ /branches/bleeding_edge/src/api.cc  Wed Dec 18 18:38:35 2013 UTC
@@ -6498,6 +6498,17 @@
   i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
   i_isolate->stack_guard()->CancelTerminateExecution();
 }
+
+
+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() {
=======================================
--- /branches/bleeding_edge/src/execution.cc    Wed Nov 27 17:21:40 2013 UTC
+++ /branches/bleeding_edge/src/execution.cc    Wed Dec 18 18:38:35 2013 UTC
@@ -538,6 +538,48 @@
     reset_limits(access);
   }
 }
+
+
+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) {
@@ -581,6 +623,7 @@
   nesting_ = 0;
   postpone_interrupts_nesting_ = 0;
   interrupt_flags_ = 0;
+  interrupt_callback_ = 0;
 }


@@ -601,6 +644,7 @@
   nesting_ = 0;
   postpone_interrupts_nesting_ = 0;
   interrupt_flags_ = 0;
+  interrupt_callback_ = 0;
   return should_set_stack_limits;
 }

@@ -935,6 +979,11 @@
   if (stack_guard->ShouldPostponeInterrupts()) {
     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,
=======================================
--- /branches/bleeding_edge/src/execution.h     Wed Nov 27 17:21:40 2013 UTC
+++ /branches/bleeding_edge/src/execution.h     Wed Dec 18 18:38:35 2013 UTC
@@ -43,7 +43,8 @@
   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 @@
   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 @@
     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
=======================================
--- /branches/bleeding_edge/src/hydrogen-instructions.h Wed Dec 18 18:05:10 2013 UTC +++ /branches/bleeding_edge/src/hydrogen-instructions.h Wed Dec 18 18:38:35 2013 UTC
@@ -1245,7 +1245,7 @@
   virtual void Verify() V8_OVERRIDE;
 #endif

-  virtual bool IsCall() { return false; }
+  virtual bool HasStackCheck() { return false; }

   DECLARE_ABSTRACT_INSTRUCTION(Instruction)

@@ -2242,8 +2242,6 @@
   virtual int argument_delta() const V8_OVERRIDE {
     return -argument_count();
   }
-
-  virtual bool IsCall() V8_FINAL V8_OVERRIDE { return true; }

  private:
   int argument_count_;
@@ -2315,6 +2313,12 @@
   HValue* function() { return second(); }
   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)

@@ -2347,6 +2351,11 @@
virtual Representation RequiredInputRepresentation(int index) V8_OVERRIDE {
     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)

@@ -2464,6 +2473,11 @@
virtual Representation RequiredInputRepresentation(int index) V8_OVERRIDE {
     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)

=======================================
--- /branches/bleeding_edge/src/hydrogen-sce.cc Mon Jul  8 07:03:57 2013 UTC
+++ /branches/bleeding_edge/src/hydrogen-sce.cc Wed Dec 18 18:38:35 2013 UTC
@@ -43,7 +43,7 @@
       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;
           }
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc Wed Dec 18 10:31:42 2013 UTC +++ /branches/bleeding_edge/test/cctest/test-api.cc Wed Dec 18 18:38:35 2013 UTC
@@ -20734,6 +20734,9 @@
 THREADED_TEST(SemaphoreInterruption) {
   ThreadInterruptTest().RunTest();
 }
+
+
+#endif  // V8_OS_POSIX


 static bool NamedAccessAlwaysBlocked(Local<v8::Object> global,
@@ -21022,7 +21025,258 @@
 }


-#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;

--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
--- You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to