Implements a new API to set a function entry hook for profiling.
authordanno@chromium.org <danno@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 12 Jul 2012 15:42:39 +0000 (15:42 +0000)
committerdanno@chromium.org <danno@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 12 Jul 2012 15:42:39 +0000 (15:42 +0000)
Exposes a new API; V8::SetFunctionEntryHook.
If a non-NULL function entry hook is set, the code generator(s) will invoke on the entry hook at the very start of each generated function.

Review URL: https://chromiumcodereview.appspot.com/10706002
Patch from Sigurður Ásgeirsson <siggi@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12069 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

14 files changed:
include/v8.h
src/api.cc
src/arm/code-stubs-arm.cc
src/arm/full-codegen-arm.cc
src/arm/lithium-codegen-arm.cc
src/code-stubs.cc
src/code-stubs.h
src/ia32/code-stubs-ia32.cc
src/ia32/full-codegen-ia32.cc
src/ia32/lithium-codegen-ia32.cc
src/x64/code-stubs-x64.cc
src/x64/full-codegen-x64.cc
src/x64/lithium-codegen-x64.cc
test/cctest/test-api.cc

index 18c5ba6807b5d2d7ec9676206a5b6285ea321c7b..34995d09f78723c933a4e9f6760473b31e1eaf45 100644 (file)
@@ -2915,15 +2915,33 @@ typedef bool (*EntropySource)(unsigned char* buffer, size_t length);
  * resolving the location of a return address on the stack. Profilers that
  * change the return address on the stack can use this to resolve the stack
  * location to whereever the profiler stashed the original return address.
- * When invoked, return_addr_location will point to a location on stack where
- * a machine return address resides, this function should return either the
- * same pointer, or a pointer to the profiler's copy of the original return
- * address.
+ *
+ * \param return_addr_location points to a location on stack where a machine
+ *    return address resides.
+ * \returns either return_addr_location, or else a pointer to the profiler's
+ *    copy of the original return address.
+ *
+ * \note the resolver function must not cause garbage collection.
  */
 typedef uintptr_t (*ReturnAddressLocationResolver)(
     uintptr_t return_addr_location);
 
 
+/**
+ * FunctionEntryHook is the type of the profile entry hook called at entry to
+ * any generated function when function-level profiling is enabled.
+ *
+ * \param function the address of the function that's being entered.
+ * \param return_addr_location points to a location on stack where the machine
+ *    return address resides. This can be used to identify the caller of
+ *    \p function, and/or modified to divert execution when \p function exits.
+ *
+ * \note the entry hook must not cause garbage collection.
+ */
+typedef void (*FunctionEntryHook)(uintptr_t function,
+                                  uintptr_t return_addr_location);
+
+
 /**
  * Interface for iterating though all external resources in the heap.
  */
@@ -3184,6 +3202,20 @@ class V8EXPORT V8 {
   static void SetReturnAddressLocationResolver(
       ReturnAddressLocationResolver return_address_resolver);
 
+  /**
+   * Allows the host application to provide the address of a function that's
+   * invoked on entry to every V8-generated function.
+   * Note that \p entry_hook is invoked at the very start of each
+   * generated function.
+   *
+   * \param entry_hook a function that will be invoked on entry to every
+   *   V8-generated function.
+   * \returns true on success on supported platforms, false on failure.
+   * \note Setting a new entry hook function when one is already active will
+   *   fail.
+   */
+  static bool SetFunctionEntryHook(FunctionEntryHook entry_hook);
+
   /**
    * Adjusts the amount of registered external memory.  Used to give
    * V8 an indication of the amount of externally allocated memory
index 4b1a3a1618228faa480cb0cd686042e359e85472..d7037bef1c2a00aee9d6f6a048c4acd257ceb46e 100644 (file)
@@ -33,6 +33,7 @@
 #include "../include/v8-profiler.h"
 #include "../include/v8-testing.h"
 #include "bootstrapper.h"
+#include "code-stubs.h"
 #include "compiler.h"
 #include "conversions-inl.h"
 #include "counters.h"
@@ -4232,6 +4233,11 @@ void v8::V8::SetReturnAddressLocationResolver(
 }
 
 
+bool v8::V8::SetFunctionEntryHook(FunctionEntryHook entry_hook) {
+  return i::ProfileEntryHookStub::SetFunctionEntryHook(entry_hook);
+}
+
+
 bool v8::V8::Dispose() {
   i::Isolate* isolate = i::Isolate::Current();
   if (!ApiCheck(isolate != NULL && isolate->IsDefaultIsolate(),
index 169a032fb030c242448f4d491174348cc892a5ad..abd55ad7889a5fa098f8ccad0e198dd14f886708 100644 (file)
@@ -7513,6 +7513,63 @@ void StoreArrayLiteralElementStub::Generate(MacroAssembler* masm) {
   __ Ret();
 }
 
+
+void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) {
+  if (entry_hook_ != NULL) {
+    ProfileEntryHookStub stub;
+    __ push(lr);
+    __ CallStub(&stub);
+    __ pop(lr);
+  }
+}
+
+
+void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
+  // The entry hook is a "push lr" instruction, followed by a call.
+  const int32_t kReturnAddressDistanceFromFunctionStart =
+      Assembler::kCallTargetAddressOffset + Assembler::kInstrSize;
+
+  // Save live volatile registers.
+  __ Push(lr, r5, r1);
+  const int32_t kNumSavedRegs = 3;
+
+  // Compute the function's address for the first argument.
+  __ sub(r0, lr, Operand(kReturnAddressDistanceFromFunctionStart));
+
+  // The caller's return address is above the saved temporaries.
+  // Grab that for the second argument to the hook.
+  __ add(r1, sp, Operand(kNumSavedRegs * kPointerSize));
+
+  // Align the stack if necessary.
+  int frame_alignment = masm->ActivationFrameAlignment();
+  if (frame_alignment > kPointerSize) {
+    __ mov(r5, sp);
+    ASSERT(IsPowerOf2(frame_alignment));
+    __ and_(sp, sp, Operand(-frame_alignment));
+  }
+
+#if defined(V8_HOST_ARCH_ARM)
+  __ mov(ip, Operand(reinterpret_cast<int32_t>(&entry_hook_)));
+  __ ldr(ip, MemOperand(ip));
+#else
+  // Under the simulator we need to indirect the entry hook through a
+  // trampoline function at a known address.
+  ApiFunction dispatcher(reinterpret_cast<Address>(EntryHookTrampoline));
+  __ mov(ip, Operand(ExternalReference(&dispatcher,
+                                       ExternalReference::BUILTIN_CALL,
+                                       masm->isolate())));
+#endif
+  __ Call(ip);
+
+  // Restore the stack pointer if needed.
+  if (frame_alignment > kPointerSize) {
+    __ mov(sp, r5);
+  }
+
+  __ Pop(lr, r5, r1);
+  __ Ret();
+}
+
 #undef __
 
 } }  // namespace v8::internal
index aadff7ada84fb5d7095e9a6a8cc064cf9bee4920..15e9d8b4acce5c6c93f7b10e64232bebd13813ad 100644 (file)
@@ -134,6 +134,8 @@ void FullCodeGenerator::Generate() {
   SetFunctionPosition(function());
   Comment cmnt(masm_, "[ function compiled by full code generator");
 
+  ProfileEntryHookStub::MaybeCallEntryHook(masm_);
+
 #ifdef DEBUG
   if (strlen(FLAG_stop_at) > 0 &&
       info->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) {
index fb687f7941404729c148968398f7f4a2d4852b7f..29588f4f368026ef6d34d8f5f3de88ec376fac96 100644 (file)
@@ -127,6 +127,8 @@ void LCodeGen::Comment(const char* format, ...) {
 bool LCodeGen::GeneratePrologue() {
   ASSERT(is_generating());
 
+  ProfileEntryHookStub::MaybeCallEntryHook(masm_);
+
 #ifdef DEBUG
   if (strlen(FLAG_stop_at) > 0 &&
       info_->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) {
index 8f316606c20d29b6889512f41939c5af1f6ecccc..f672d33e2124cf880c599cc28187b0cca234c9a4 100644 (file)
@@ -470,4 +470,26 @@ void ElementsTransitionAndStoreStub::Generate(MacroAssembler* masm) {
   KeyedStoreIC::GenerateRuntimeSetProperty(masm, strict_mode_);
 }
 
+
+FunctionEntryHook ProfileEntryHookStub::entry_hook_ = NULL;
+
+
+void ProfileEntryHookStub::EntryHookTrampoline(intptr_t function,
+                                               intptr_t stack_pointer) {
+  if (entry_hook_ != NULL)
+    entry_hook_(function, stack_pointer);
+}
+
+
+bool ProfileEntryHookStub::SetFunctionEntryHook(FunctionEntryHook entry_hook) {
+  // We don't allow setting a new entry hook over one that's
+  // already active, as the hooks won't stack.
+  if (entry_hook != 0 && entry_hook_ != 0)
+    return false;
+
+  entry_hook_ = entry_hook;
+  return true;
+}
+
+
 } }  // namespace v8::internal
index 5a076d2f1387fba5b3d16f7b9935867aab99a5f9..f19063230a7d953d7dabe5c152c6e0eaf8798bc9 100644 (file)
@@ -73,7 +73,8 @@ namespace internal {
   V(DebuggerStatement)                   \
   V(StringDictionaryLookup)              \
   V(ElementsTransitionAndStore)          \
-  V(StoreArrayLiteralElement)
+  V(StoreArrayLiteralElement)            \
+  V(ProfileEntryHook)
 
 // List of code stubs only used on ARM platforms.
 #ifdef V8_TARGET_ARCH_ARM
@@ -1142,6 +1143,37 @@ class StoreArrayLiteralElementStub : public CodeStub {
   DISALLOW_COPY_AND_ASSIGN(StoreArrayLiteralElementStub);
 };
 
+
+class ProfileEntryHookStub : public CodeStub {
+ public:
+  explicit ProfileEntryHookStub() {}
+
+  // The profile entry hook function is not allowed to cause a GC.
+  virtual bool SometimesSetsUpAFrame() { return false; }
+
+  // Generates a call to the entry hook if it's enabled.
+  static void MaybeCallEntryHook(MacroAssembler* masm);
+
+  // Sets or unsets the entry hook function. Returns true on success,
+  // false on an attempt to replace a non-NULL entry hook with another
+  // non-NULL hook.
+  static bool SetFunctionEntryHook(FunctionEntryHook entry_hook);
+
+ private:
+  static void EntryHookTrampoline(intptr_t function,
+                                  intptr_t stack_pointer);
+
+  Major MajorKey() { return ProfileEntryHook; }
+  int MinorKey() { return 0; }
+
+  void Generate(MacroAssembler* masm);
+
+  // The current function entry hook.
+  static FunctionEntryHook entry_hook_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProfileEntryHookStub);
+};
+
 } }  // namespace v8::internal
 
 #endif  // V8_CODE_STUBS_H_
index afa3e1c568bdfadbe9e3feb88d51b2efaa56118a..7be2b4f65f9d8a5a06c7ec651452f1192f7199e8 100644 (file)
@@ -7474,6 +7474,38 @@ void StoreArrayLiteralElementStub::Generate(MacroAssembler* masm) {
   __ ret(0);
 }
 
+
+void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) {
+  if (entry_hook_ != NULL) {
+    ProfileEntryHookStub stub;
+    masm->CallStub(&stub);
+  }
+}
+
+
+void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
+  // Ecx is the only volatile register we must save.
+  __ push(ecx);
+
+  // Calculate and push the original stack pointer.
+  __ lea(eax, Operand(esp, kPointerSize));
+  __ push(eax);
+
+  // Calculate and push the function address.
+  __ mov(eax, Operand(eax, 0));
+  __ sub(eax, Immediate(Assembler::kCallInstructionLength));
+  __ push(eax);
+
+  // Call the entry hook.
+  int32_t hook_location = reinterpret_cast<int32_t>(&entry_hook_);
+  __ call(Operand(hook_location, RelocInfo::NONE));
+  __ add(esp, Immediate(2 * kPointerSize));
+
+  // Restore ecx.
+  __ pop(ecx);
+  __ ret(0);
+}
+
 #undef __
 
 } }  // namespace v8::internal
index 2867d5ec7203c1ab76b725ed6f0187e46e0dd4a7..5daf94029f96f199eb32a56ce52c821e062a0b8c 100644 (file)
@@ -123,6 +123,8 @@ void FullCodeGenerator::Generate() {
   SetFunctionPosition(function());
   Comment cmnt(masm_, "[ function compiled by full code generator");
 
+  ProfileEntryHookStub::MaybeCallEntryHook(masm_);
+
 #ifdef DEBUG
   if (strlen(FLAG_stop_at) > 0 &&
       info->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) {
@@ -4550,7 +4552,6 @@ FullCodeGenerator::NestedStatement* FullCodeGenerator::TryFinally::Exit(
   return previous_;
 }
 
-
 #undef __
 
 } }  // namespace v8::internal
index 6317a54a2b400385c2c75ff3c59b6fac8ca36deb..4abdc8b5d401e0c62ae1eee3a38e2d54b0d0b6db 100644 (file)
@@ -135,6 +135,8 @@ void LCodeGen::Comment(const char* format, ...) {
 bool LCodeGen::GeneratePrologue() {
   ASSERT(is_generating());
 
+  ProfileEntryHookStub::MaybeCallEntryHook(masm_);
+
 #ifdef DEBUG
   if (strlen(FLAG_stop_at) > 0 &&
       info_->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) {
index ecdb3926c04dfb321a739312128ed5991904d5af..28157ddb77360e323aab7d37e6c60e1bf82c16c3 100644 (file)
@@ -6420,6 +6420,40 @@ void StoreArrayLiteralElementStub::Generate(MacroAssembler* masm) {
   __ ret(0);
 }
 
+
+void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) {
+  if (entry_hook_ != NULL) {
+    ProfileEntryHookStub stub;
+    masm->CallStub(&stub);
+  }
+}
+
+
+void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
+  // RCX is the only volatile register we must save.
+  __ push(rcx);
+
+  // Calculate the original stack pointer and store it in RDX, the second arg.
+  __ lea(rdx, Operand(rsp, kPointerSize));
+
+  // Calculate the function address to RCX, the first arg.
+  __ movq(rcx, Operand(rdx, 0));
+  __ subq(rcx, Immediate(Assembler::kShortCallInstructionLength));
+
+  // Reserve stack for the first 4 args.
+  __ subq(rsp, Immediate(4 * kPointerSize));
+
+  // Call the entry hook.
+  int64_t hook_location = reinterpret_cast<int64_t>(&entry_hook_);
+  __ movq(rax, hook_location, RelocInfo::NONE);
+  __ call(Operand(rax, 0));
+  __ addq(rsp, Immediate(4 * kPointerSize));
+
+  // Restore RCX.
+  __ pop(rcx);
+  __ ret(0);
+}
+
 #undef __
 
 } }  // namespace v8::internal
index 1259160c5efb36da624c95a456f8436a86a90930..b27412f5507aec5aa7ea993aac2df8f702d4c057 100644 (file)
@@ -123,6 +123,8 @@ void FullCodeGenerator::Generate() {
   SetFunctionPosition(function());
   Comment cmnt(masm_, "[ function compiled by full code generator");
 
+  ProfileEntryHookStub::MaybeCallEntryHook(masm_);
+
 #ifdef DEBUG
   if (strlen(FLAG_stop_at) > 0 &&
       info->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) {
index 68e43964e0c5ef9fb97891386084fc97405d183b..461b632efd3ee87d9d9f79f06009dcfc83ec8102 100644 (file)
@@ -128,6 +128,8 @@ void LCodeGen::Comment(const char* format, ...) {
 bool LCodeGen::GeneratePrologue() {
   ASSERT(is_generating());
 
+  ProfileEntryHookStub::MaybeCallEntryHook(masm_);
+
 #ifdef DEBUG
   if (strlen(FLAG_stop_at) > 0 &&
       info_->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) {
index 229607266004673f87054898df272440f22105f0..57cffae8fe3142010f4430697dc2bd0d91d84d81 100644 (file)
@@ -33,6 +33,7 @@
 #include "isolate.h"
 #include "compilation-cache.h"
 #include "execution.h"
+#include "objects.h"
 #include "snapshot.h"
 #include "platform.h"
 #include "utils.h"
@@ -10873,6 +10874,110 @@ THREADED_TEST(NestedHandleScopeAndContexts) {
 }
 
 
+static i::Handle<i::JSFunction>* foo_ptr = NULL;
+static int foo_count = 0;
+static i::Handle<i::JSFunction>* bar_ptr = NULL;
+static int bar_count = 0;
+
+
+static void entry_hook(uintptr_t function,
+                       uintptr_t return_addr_location) {
+  i::Code* code = i::Code::GetCodeFromTargetAddress(
+      reinterpret_cast<i::Address>(function));
+  CHECK(code != NULL);
+
+  if (bar_ptr != NULL && code == (*bar_ptr)->code())
+    ++bar_count;
+
+  if (foo_ptr != NULL && code == (*foo_ptr)->code())
+    ++foo_count;
+
+  // TODO(siggi): Verify return_addr_location.
+}
+
+
+static void RunLoopInNewEnv() {
+  bar_ptr = NULL;
+  foo_ptr = NULL;
+
+  v8::HandleScope outer;
+  v8::Persistent<Context> env = Context::New();
+  env->Enter();
+
+  const char* script =
+      "function bar() {"
+      "  var sum = 0;"
+      "  for (i = 0; i < 100; ++i)"
+      "    sum = foo(i);"
+      "  return sum;"
+      "}"
+      "function foo(i) { return i * i; }";
+  CompileRun(script);
+  i::Handle<i::JSFunction> bar =
+      i::Handle<i::JSFunction>::cast(
+          v8::Utils::OpenHandle(*env->Global()->Get(v8_str("bar"))));
+  ASSERT(*bar);
+
+  i::Handle<i::JSFunction> foo =
+      i::Handle<i::JSFunction>::cast(
+           v8::Utils::OpenHandle(*env->Global()->Get(v8_str("foo"))));
+  ASSERT(*foo);
+
+  bar_ptr = &bar;
+  foo_ptr = &foo;
+
+  v8::Handle<v8::Value> value = CompileRun("bar();");
+  CHECK(value->IsNumber());
+  CHECK_EQ(9801.0, v8::Number::Cast(*value)->Value());
+
+  // Test the optimized codegen path.
+  value = CompileRun("%OptimizeFunctionOnNextCall(foo);"
+                     "bar();");
+  CHECK(value->IsNumber());
+  CHECK_EQ(9801.0, v8::Number::Cast(*value)->Value());
+
+  env->Exit();
+}
+
+
+THREADED_TEST(SetFunctionEntryHook) {
+  i::FLAG_allow_natives_syntax = true;
+
+  // Test setting and resetting the entry hook.
+  // Nulling it should always succeed.
+  CHECK(v8::V8::SetFunctionEntryHook(NULL));
+
+  CHECK(v8::V8::SetFunctionEntryHook(entry_hook));
+  // Setting a hook while one's active should fail.
+  CHECK_EQ(false, v8::V8::SetFunctionEntryHook(entry_hook));
+
+  CHECK(v8::V8::SetFunctionEntryHook(NULL));
+
+  // Reset the entry count to zero and set the entry hook.
+  bar_count = 0;
+  foo_count = 0;
+  CHECK(v8::V8::SetFunctionEntryHook(entry_hook));
+  RunLoopInNewEnv();
+
+  CHECK_EQ(2, bar_count);
+  CHECK_EQ(200, foo_count);
+
+  // Clear the entry hook and count.
+  bar_count = 0;
+  foo_count = 0;
+  v8::V8::SetFunctionEntryHook(NULL);
+
+  // Clear the compilation cache to make sure we don't reuse the
+  // functions from the previous invocation.
+  v8::internal::Isolate::Current()->compilation_cache()->Clear();
+
+  // Verify that entry hooking is now disabled.
+  RunLoopInNewEnv();
+  CHECK(0u == bar_count);
+  CHECK(0u == foo_count);
+}
+
+
 static int64_t cast(intptr_t x) { return static_cast<int64_t>(x); }