Revision: 18375
Author:   [email protected]
Date:     Thu Dec 19 16:45:58 2013 UTC
Log:      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.

[email protected]

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

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 19:21:27 2013 UTC
+++ /branches/bleeding_edge/include/v8.h        Thu Dec 19 16:45:58 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 19:21:27 2013 UTC
+++ /branches/bleeding_edge/src/api.cc  Thu Dec 19 16:45:58 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 Dec 18 19:21:27 2013 UTC
+++ /branches/bleeding_edge/src/execution.cc    Thu Dec 19 16:45:58 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 Dec 18 19:21:27 2013 UTC
+++ /branches/bleeding_edge/src/execution.h     Thu Dec 19 16:45:58 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 19:21:27 2013 UTC +++ /branches/bleeding_edge/src/hydrogen-instructions.h Thu Dec 19 16:45:58 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_;
@@ -2300,6 +2298,9 @@
         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,
@@ -2315,16 +2316,22 @@
   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 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_;
 };


@@ -2347,6 +2354,10 @@
virtual Representation RequiredInputRepresentation(int index) V8_OVERRIDE {
     return Representation::None();
   }
+
+  virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE {
+    return has_stack_check_;
+  }

   DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction)

@@ -2354,10 +2365,14 @@
   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_;
 };


@@ -2464,6 +2479,10 @@
virtual Representation RequiredInputRepresentation(int index) V8_OVERRIDE {
     return Representation::None();
   }
+
+  virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE {
+    return has_stack_check_;
+  }

   DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal)

@@ -2471,10 +2490,14 @@
   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_;
 };


=======================================
--- /branches/bleeding_edge/src/hydrogen-sce.cc Wed Dec 18 19:21:27 2013 UTC
+++ /branches/bleeding_edge/src/hydrogen-sce.cc Thu Dec 19 16:45:58 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 19:21:27 2013 UTC +++ /branches/bleeding_edge/test/cctest/test-api.cc Thu Dec 19 16:45:58 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,255 @@
 }


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

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