Add a new API V8::SetJitCodeEventHandler to push code name and location to users...
authorjkummerow@chromium.org <jkummerow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 27 Aug 2012 18:03:38 +0000 (18:03 +0000)
committerjkummerow@chromium.org <jkummerow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 27 Aug 2012 18:03:38 +0000 (18:03 +0000)
BUG=None
TEST=Included in CL.

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

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

include/v8.h
src/api.cc
src/compiler.cc
src/cpu-profiler.h
src/heap.cc
src/isolate.cc
src/log.cc
src/log.h
src/runtime.cc
src/serialize.cc
test/cctest/test-api.cc

index a7b5254..111f9cf 100644 (file)
@@ -2945,6 +2945,57 @@ typedef void (*FunctionEntryHook)(uintptr_t function,
 
 
 /**
+ * A JIT code event is issued each time code is added, moved or removed.
+ *
+ * \note removal events are not currently issued.
+ */
+struct JitCodeEvent {
+  enum EventType {
+    CODE_ADDED,
+    CODE_MOVED,
+    CODE_REMOVED
+  };
+
+  // Type of event.
+  EventType type;
+  // Start of the instructions.
+  void* code_start;
+  // Size of the instructions.
+  size_t code_len;
+
+  union {
+    // Only valid for CODE_ADDED.
+    struct {
+      // Name of the object associated with the code, note that the string is
+      // not zero-terminated.
+      const char* str;
+      // Number of chars in str.
+      size_t len;
+    } name;
+    // New location of instructions. Only valid for CODE_MOVED.
+    void* new_code_start;
+  };
+};
+
+/**
+ * Option flags passed to the SetJitCodeEventHandler function.
+ */
+enum JitCodeEventOptions {
+  kJitCodeEventDefault = 0,
+  // Generate callbacks for already existent code.
+  kJitCodeEventEnumExisting = 1
+};
+
+
+/**
+ * Callback function passed to SetJitCodeEventHandler.
+ *
+ * \param event code add, move or removal event.
+ */
+typedef void (*JitCodeEventHandler)(const JitCodeEvent* event);
+
+
+/**
  * Interface for iterating though all external resources in the heap.
  */
 class V8EXPORT ExternalResourceVisitor {  // NOLINT
@@ -3219,6 +3270,29 @@ class V8EXPORT V8 {
   static bool SetFunctionEntryHook(FunctionEntryHook entry_hook);
 
   /**
+   * Allows the host application to provide the address of a function that is
+   * notified each time code is added, moved or removed.
+   *
+   * \param options options for the JIT code event handler.
+   * \param event_handler the JIT code event handler, which will be invoked
+   *     each time code is added, moved or removed.
+   * \note \p event_handler won't get notified of existent code.
+   * \note since code removal notifications are not currently issued, the
+   *     \p event_handler may get notifications of code that overlaps earlier
+   *     code notifications. This happens when code areas are reused, and the
+   *     earlier overlapping code areas should therefore be discarded.
+   * \note the events passed to \p event_handler and the strings they point to
+   *     are not guaranteed to live past each call. The \p event_handler must
+   *     copy strings and other parameters it needs to keep around.
+   * \note the set of events declared in JitCodeEvent::EventType is expected to
+   *     grow over time, and the JitCodeEvent structure is expected to accrue
+   *     new members. The \p event_handler function must ignore event codes
+   *     it does not recognize to maintain future compatibility.
+   */
+  static void SetJitCodeEventHandler(JitCodeEventOptions options,
+                                     JitCodeEventHandler event_handler);
+
+  /**
    * Adjusts the amount of registered external memory.  Used to give
    * V8 an indication of the amount of externally allocated memory
    * that is kept alive by JavaScript objects.  V8 uses this to decide
index 0da6ab7..0c92835 100644 (file)
@@ -4262,6 +4262,15 @@ bool v8::V8::SetFunctionEntryHook(FunctionEntryHook entry_hook) {
 }
 
 
+void v8::V8::SetJitCodeEventHandler(
+    JitCodeEventOptions options, JitCodeEventHandler event_handler) {
+  i::Isolate* isolate = i::Isolate::Current();
+  // Ensure that logging is initialized for our isolate.
+  isolate->InitializeLoggingAndCounters();
+  isolate->logger()->SetCodeEventHandler(options, event_handler);
+}
+
+
 bool v8::V8::Dispose() {
   i::Isolate* isolate = i::Isolate::Current();
   if (!ApiCheck(isolate != NULL && isolate->IsDefaultIsolate(),
index 88510d5..fac63d5 100644 (file)
@@ -1003,7 +1003,7 @@ void Compiler::RecordFunctionCompilation(Logger::LogEventsAndTags tag,
   // Log the code generation. If source information is available include
   // script name and line number. Check explicitly whether logging is
   // enabled as finding the line number is not free.
-  if (info->isolate()->logger()->is_logging() ||
+  if (info->isolate()->logger()->is_logging_code_events() ||
       CpuProfiler::is_profiling(info->isolate())) {
     Handle<Script> script = info->script();
     Handle<Code> code = info->code();
index 6e2e771..9cd4484 100644 (file)
@@ -188,7 +188,7 @@ class ProfilerEventsProcessor : public Thread {
 
 
 #define PROFILE(isolate, Call)                                \
-  LOG(isolate, Call);                                         \
+  LOG_CODE_EVENT(isolate, Call);                              \
   do {                                                        \
     if (v8::internal::CpuProfiler::is_profiling(isolate)) {   \
       v8::internal::CpuProfiler::Call;                        \
index 694d9fd..45d58e7 100644 (file)
@@ -1741,7 +1741,7 @@ class ScavengingVisitor : public StaticVisitorBase {
       RecordCopiedObject(heap, target);
       HEAP_PROFILE(heap, ObjectMoveEvent(source->address(), target->address()));
       Isolate* isolate = heap->isolate();
-      if (isolate->logger()->is_logging() ||
+      if (isolate->logger()->is_logging_code_events() ||
           CpuProfiler::is_profiling(isolate)) {
         if (target->IsSharedFunctionInfo()) {
           PROFILE(isolate, SharedFunctionInfoMoveEvent(
index 76bfc9b..347f2a7 100644 (file)
@@ -1902,7 +1902,8 @@ bool Isolate::Init(Deserializer* des) {
 
   // If we are deserializing, log non-function code objects and compiled
   // functions found in the snapshot.
-  if (create_heap_objects && (FLAG_log_code || FLAG_ll_prof)) {
+  if (create_heap_objects &&
+      (FLAG_log_code || FLAG_ll_prof || logger_->is_logging_code_events())) {
     HandleScope scope;
     LOG(this, LogCodeObjects());
     LOG(this, LogCompiledFunctions());
index 6c07758..bf44cc0 100644 (file)
@@ -526,6 +526,7 @@ Logger::Logger()
     name_buffer_(new NameBuffer),
     address_to_name_map_(NULL),
     is_initialized_(false),
+    code_event_handler_(NULL),
     last_address_(NULL),
     prev_sp_(NULL),
     prev_function_(NULL),
@@ -541,6 +542,50 @@ Logger::~Logger() {
 }
 
 
+void Logger::IssueCodeAddedEvent(Code* code,
+                                 const char* name,
+                                 size_t name_len) {
+  JitCodeEvent event;
+  event.type = JitCodeEvent::CODE_ADDED;
+  event.code_start = code->instruction_start();
+  event.code_len = code->instruction_size();
+  event.name.str = name;
+  event.name.len = name_len;
+
+  code_event_handler_(&event);
+}
+
+
+void Logger::IssueCodeMovedEvent(Address from, Address to) {
+  Code* from_code = Code::cast(HeapObject::FromAddress(from));
+
+  JitCodeEvent event;
+  event.type = JitCodeEvent::CODE_MOVED;
+  event.code_start = from_code->instruction_start();
+  event.code_len = from_code->instruction_size();
+
+  // Calculate the header size.
+  const size_t header_size =
+      from_code->instruction_start() - reinterpret_cast<byte*>(from_code);
+
+  event.new_code_start = to + header_size;
+
+  code_event_handler_(&event);
+}
+
+
+void Logger::IssueCodeRemovedEvent(Address from) {
+  Code* from_code = Code::cast(HeapObject::FromAddress(from));
+
+  JitCodeEvent event;
+  event.type = JitCodeEvent::CODE_REMOVED;
+  event.code_start = from_code->instruction_start();
+  event.code_len = from_code->instruction_size();
+
+  code_event_handler_(&event);
+}
+
+
 #define DECLARE_EVENT(ignore1, name) name,
 static const char* const kLogEventsNames[Logger::NUMBER_OF_LOG_EVENTS] = {
   LOG_EVENTS_AND_TAGS_LIST(DECLARE_EVENT)
@@ -864,13 +909,17 @@ void Logger::SetterCallbackEvent(String* name, Address entry_point) {
 void Logger::CodeCreateEvent(LogEventsAndTags tag,
                              Code* code,
                              const char* comment) {
-  if (!log_->IsEnabled()) return;
-  if (FLAG_ll_prof || Serializer::enabled()) {
+  if (!is_logging_code_events()) return;
+  if (FLAG_ll_prof || Serializer::enabled() || code_event_handler_ != NULL) {
     name_buffer_->Reset();
     name_buffer_->AppendBytes(kLogEventsNames[tag]);
     name_buffer_->AppendByte(':');
     name_buffer_->AppendBytes(comment);
   }
+  if (code_event_handler_ != NULL) {
+    IssueCodeAddedEvent(code, name_buffer_->get(), name_buffer_->size());
+  }
+  if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) {
     LowLevelCodeCreateEvent(code, name_buffer_->get(), name_buffer_->size());
   }
@@ -899,13 +948,17 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
 void Logger::CodeCreateEvent(LogEventsAndTags tag,
                              Code* code,
                              String* name) {
-  if (!log_->IsEnabled()) return;
-  if (FLAG_ll_prof || Serializer::enabled()) {
+  if (!is_logging_code_events()) return;
+  if (FLAG_ll_prof || Serializer::enabled() || code_event_handler_ != NULL) {
     name_buffer_->Reset();
     name_buffer_->AppendBytes(kLogEventsNames[tag]);
     name_buffer_->AppendByte(':');
     name_buffer_->AppendString(name);
   }
+  if (code_event_handler_ != NULL) {
+    IssueCodeAddedEvent(code, name_buffer_->get(), name_buffer_->size());
+  }
+  if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) {
     LowLevelCodeCreateEvent(code, name_buffer_->get(), name_buffer_->size());
   }
@@ -940,14 +993,18 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
                              Code* code,
                              SharedFunctionInfo* shared,
                              String* name) {
-  if (!log_->IsEnabled()) return;
-  if (FLAG_ll_prof || Serializer::enabled()) {
+  if (!is_logging_code_events()) return;
+  if (FLAG_ll_prof || Serializer::enabled() || code_event_handler_ != NULL) {
     name_buffer_->Reset();
     name_buffer_->AppendBytes(kLogEventsNames[tag]);
     name_buffer_->AppendByte(':');
     name_buffer_->AppendBytes(ComputeMarker(code));
     name_buffer_->AppendString(name);
   }
+  if (code_event_handler_ != NULL) {
+    IssueCodeAddedEvent(code, name_buffer_->get(), name_buffer_->size());
+  }
+  if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) {
     LowLevelCodeCreateEvent(code, name_buffer_->get(), name_buffer_->size());
   }
@@ -981,8 +1038,8 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
                              Code* code,
                              SharedFunctionInfo* shared,
                              String* source, int line) {
-  if (!log_->IsEnabled()) return;
-  if (FLAG_ll_prof || Serializer::enabled()) {
+  if (!is_logging_code_events()) return;
+  if (FLAG_ll_prof || Serializer::enabled() || code_event_handler_ != NULL) {
     name_buffer_->Reset();
     name_buffer_->AppendBytes(kLogEventsNames[tag]);
     name_buffer_->AppendByte(':');
@@ -993,6 +1050,10 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
     name_buffer_->AppendByte(':');
     name_buffer_->AppendInt(line);
   }
+  if (code_event_handler_ != NULL) {
+    IssueCodeAddedEvent(code, name_buffer_->get(), name_buffer_->size());
+  }
+  if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) {
     LowLevelCodeCreateEvent(code, name_buffer_->get(), name_buffer_->size());
   }
@@ -1022,13 +1083,17 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag,
 
 
 void Logger::CodeCreateEvent(LogEventsAndTags tag, Code* code, int args_count) {
-  if (!log_->IsEnabled()) return;
-  if (FLAG_ll_prof || Serializer::enabled()) {
+  if (!is_logging_code_events()) return;
+  if (FLAG_ll_prof || Serializer::enabled() || code_event_handler_ != NULL) {
     name_buffer_->Reset();
     name_buffer_->AppendBytes(kLogEventsNames[tag]);
     name_buffer_->AppendByte(':');
     name_buffer_->AppendInt(args_count);
   }
+  if (code_event_handler_ != NULL) {
+    IssueCodeAddedEvent(code, name_buffer_->get(), name_buffer_->size());
+  }
+  if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) {
     LowLevelCodeCreateEvent(code, name_buffer_->get(), name_buffer_->size());
   }
@@ -1055,13 +1120,17 @@ void Logger::CodeMovingGCEvent() {
 
 
 void Logger::RegExpCodeCreateEvent(Code* code, String* source) {
-  if (!log_->IsEnabled()) return;
-  if (FLAG_ll_prof || Serializer::enabled()) {
+  if (!is_logging_code_events()) return;
+  if (FLAG_ll_prof || Serializer::enabled() || code_event_handler_ != NULL) {
     name_buffer_->Reset();
     name_buffer_->AppendBytes(kLogEventsNames[REG_EXP_TAG]);
     name_buffer_->AppendByte(':');
     name_buffer_->AppendString(source);
   }
+  if (code_event_handler_ != NULL) {
+    IssueCodeAddedEvent(code, name_buffer_->get(), name_buffer_->size());
+  }
+  if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) {
     LowLevelCodeCreateEvent(code, name_buffer_->get(), name_buffer_->size());
   }
@@ -1083,6 +1152,7 @@ void Logger::RegExpCodeCreateEvent(Code* code, String* source) {
 
 
 void Logger::CodeMoveEvent(Address from, Address to) {
+  if (code_event_handler_ != NULL) IssueCodeMovedEvent(from, to);
   if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) LowLevelCodeMoveEvent(from, to);
   if (Serializer::enabled() && address_to_name_map_ != NULL) {
@@ -1093,6 +1163,7 @@ void Logger::CodeMoveEvent(Address from, Address to) {
 
 
 void Logger::CodeDeleteEvent(Address from) {
+  if (code_event_handler_ != NULL) IssueCodeRemovedEvent(from);
   if (!log_->IsEnabled()) return;
   if (FLAG_ll_prof) LowLevelCodeDeleteEvent(from);
   if (Serializer::enabled() && address_to_name_map_ != NULL) {
@@ -1392,7 +1463,7 @@ static int EnumerateCompiledFunctions(Handle<SharedFunctionInfo>* sfis,
 
 
 void Logger::LogCodeObject(Object* object) {
-  if (FLAG_log_code || FLAG_ll_prof) {
+  if (FLAG_log_code || FLAG_ll_prof || is_logging_code_events()) {
     Code* code_object = Code::cast(object);
     LogEventsAndTags tag = Logger::STUB_TAG;
     const char* description = "Unknown code from the snapshot";
@@ -1676,6 +1747,18 @@ bool Logger::SetUp() {
 }
 
 
+void Logger::SetCodeEventHandler(uint32_t options,
+                                 JitCodeEventHandler event_handler) {
+  code_event_handler_ = event_handler;
+
+  if (code_event_handler_ != NULL && (options & kJitCodeEventEnumExisting)) {
+    HandleScope scope;
+    LogCodeObjects();
+    LogCompiledFunctions();
+  }
+}
+
+
 Sampler* Logger::sampler() {
   return ticker_;
 }
index 03c7b3b..33f359a 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -86,6 +86,15 @@ class Ticker;
       logger->Call;                                 \
   } while (false)
 
+#define LOG_CODE_EVENT(isolate, Call)               \
+  do {                                              \
+    v8::internal::Logger* logger =                  \
+        (isolate)->logger();                        \
+    if (logger->is_logging_code_events())           \
+      logger->Call;                                 \
+  } while (false)
+
+
 #define LOG_EVENTS_AND_TAGS_LIST(V)                                     \
   V(CODE_CREATION_EVENT,            "code-creation")                    \
   V(CODE_MOVE_EVENT,                "code-move")                        \
@@ -151,6 +160,10 @@ class Logger {
   // Acquires resources for logging if the right flags are set.
   bool SetUp();
 
+  // Sets the current code event handler.
+  void SetCodeEventHandler(uint32_t options,
+                           JitCodeEventHandler event_handler);
+
   void EnsureTickerStarted();
   void EnsureTickerStopped();
 
@@ -274,6 +287,10 @@ class Logger {
     return logging_nesting_ > 0;
   }
 
+  bool is_logging_code_events() {
+    return is_logging() || code_event_handler_ != NULL;
+  }
+
   // Pause/Resume collection of profiling data.
   // When data collection is paused, CPU Tick events are discarded until
   // data collection is Resumed.
@@ -312,6 +329,11 @@ class Logger {
   Logger();
   ~Logger();
 
+  // Issue code notifications.
+  void IssueCodeAddedEvent(Code* code, const char* name, size_t name_len);
+  void IssueCodeMovedEvent(Address from, Address to);
+  void IssueCodeRemovedEvent(Address from);
+
   // Emits the profiler's first message.
   void ProfilerBeginEvent();
 
@@ -413,6 +435,9 @@ class Logger {
   // 'true' between SetUp() and TearDown().
   bool is_initialized_;
 
+  // The code event handler - if any.
+  JitCodeEventHandler code_event_handler_;
+
   // Support for 'incremental addresses' in compressed logs:
   //  LogMessageBuilder::AppendAddress(Address addr)
   Address last_address_;
index 5b0bfc3..349be80 100644 (file)
@@ -2269,7 +2269,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetCode) {
   target->set_literals(*literals);
   target->set_next_function_link(isolate->heap()->undefined_value());
 
-  if (isolate->logger()->is_logging() || CpuProfiler::is_profiling(isolate)) {
+  if (isolate->logger()->is_logging_code_events() ||
+      CpuProfiler::is_profiling(isolate)) {
     isolate->logger()->LogExistingFunction(
         source_shared, Handle<Code>(source_shared->code()));
   }
index efe0619..792f25c 100644 (file)
@@ -679,29 +679,35 @@ HeapObject* Deserializer::GetAddressFromStart(int space) {
 void Deserializer::Deserialize() {
   isolate_ = Isolate::Current();
   ASSERT(isolate_ != NULL);
-  // Don't GC while deserializing - just expand the heap.
-  AlwaysAllocateScope always_allocate;
-  // Don't use the free lists while deserializing.
-  LinearAllocationScope allocate_linearly;
-  // No active threads.
-  ASSERT_EQ(NULL, isolate_->thread_manager()->FirstThreadStateInUse());
-  // No active handles.
-  ASSERT(isolate_->handle_scope_implementer()->blocks()->is_empty());
-  ASSERT_EQ(NULL, external_reference_decoder_);
-  external_reference_decoder_ = new ExternalReferenceDecoder();
-  isolate_->heap()->IterateStrongRoots(this, VISIT_ONLY_STRONG);
-  isolate_->heap()->IterateWeakRoots(this, VISIT_ALL);
-
-  isolate_->heap()->set_native_contexts_list(
-      isolate_->heap()->undefined_value());
-
-  // Update data pointers to the external strings containing natives sources.
-  for (int i = 0; i < Natives::GetBuiltinsCount(); i++) {
-    Object* source = isolate_->heap()->natives_source_cache()->get(i);
-    if (!source->IsUndefined()) {
-      ExternalAsciiString::cast(source)->update_data_cache();
+  {
+    // Don't GC while deserializing - just expand the heap.
+    AlwaysAllocateScope always_allocate;
+    // Don't use the free lists while deserializing.
+    LinearAllocationScope allocate_linearly;
+    // No active threads.
+    ASSERT_EQ(NULL, isolate_->thread_manager()->FirstThreadStateInUse());
+    // No active handles.
+    ASSERT(isolate_->handle_scope_implementer()->blocks()->is_empty());
+    ASSERT_EQ(NULL, external_reference_decoder_);
+    external_reference_decoder_ = new ExternalReferenceDecoder();
+    isolate_->heap()->IterateStrongRoots(this, VISIT_ONLY_STRONG);
+    isolate_->heap()->IterateWeakRoots(this, VISIT_ALL);
+
+    isolate_->heap()->set_native_contexts_list(
+        isolate_->heap()->undefined_value());
+
+    // Update data pointers to the external strings containing natives sources.
+    for (int i = 0; i < Natives::GetBuiltinsCount(); i++) {
+      Object* source = isolate_->heap()->natives_source_cache()->get(i);
+      if (!source->IsUndefined()) {
+        ExternalAsciiString::cast(source)->update_data_cache();
+      }
     }
   }
+
+  // Issue code events for newly deserialized code objects.
+  LOG_CODE_EVENT(isolate_, LogCodeObjects());
+  LOG_CODE_EVENT(isolate_, LogCompiledFunctions());
 }
 
 
@@ -714,7 +720,17 @@ void Deserializer::DeserializePartial(Object** root) {
   if (external_reference_decoder_ == NULL) {
     external_reference_decoder_ = new ExternalReferenceDecoder();
   }
+
+  // Keep track of the code space start and end pointers in case new
+  // code objects were unserialized
+  OldSpace* code_space = isolate_->heap()->code_space();
+  Address start_address = code_space->top();
   VisitPointer(root);
+
+  // There's no code deserialized here. If this assert fires
+  // then that's changed and logging should be added to notify
+  // the profiler et al of the new code.
+  CHECK_EQ(start_address, code_space->top());
 }
 
 
index d487a5b..478a3ca 100644 (file)
@@ -10981,6 +10981,8 @@ static void entry_hook(uintptr_t function,
     ++foo_count;
 
   // TODO(siggi): Verify return_addr_location.
+  //     This can be done by capturing JitCodeEvents, but requires an ordered
+  //     collection.
 }
 
 
@@ -11066,6 +11068,188 @@ TEST(SetFunctionEntryHook) {
 }
 
 
+static i::HashMap* code_map = NULL;
+static int saw_bar = 0;
+static int move_events = 0;
+
+
+static bool FunctionNameIs(const char* expected,
+                           const v8::JitCodeEvent* event) {
+  // Log lines for functions are of the general form:
+  // "LazyCompile:<type><function_name>", where the type is one of
+  // "*", "~" or "".
+  static const char kPreamble[] = "LazyCompile:";
+  static size_t kPreambleLen = sizeof(kPreamble) - 1;
+
+  if (event->name.len < sizeof(kPreamble) - 1 ||
+      strncmp(kPreamble, event->name.str, kPreambleLen) != 0) {
+    return false;
+  }
+
+  const char* tail = event->name.str + kPreambleLen;
+  size_t tail_len = event->name.len - kPreambleLen;
+  size_t expected_len = strlen(expected);
+  if (tail_len == expected_len + 1) {
+    if (*tail == '*' || *tail == '~') {
+      --tail_len;
+      ++tail;
+    } else {
+      return false;
+    }
+  }
+
+  if (tail_len != expected_len)
+    return false;
+
+  return strncmp(tail, expected, expected_len) == 0;
+}
+
+
+static void event_handler(const v8::JitCodeEvent* event) {
+  CHECK(event != NULL);
+  CHECK(code_map != NULL);
+
+  uint32_t hash = static_cast<uint32_t>(
+      reinterpret_cast<uintptr_t>(event->code_start));
+  switch (event->type) {
+    case v8::JitCodeEvent::CODE_ADDED: {
+        CHECK(event->code_start != NULL);
+        CHECK_NE(0, event->code_len);
+        CHECK(event->name.str != NULL);
+        i::HashMap::Entry* entry =
+            code_map->Lookup(event->code_start, hash, true);
+        entry->value = reinterpret_cast<void*>(event->code_len);
+
+        if (FunctionNameIs("bar", event)) {
+          ++saw_bar;
+        }
+      }
+      break;
+
+    case v8::JitCodeEvent::CODE_MOVED: {
+        ++move_events;
+
+        // We should never see code move that we haven't seen before.
+        i::HashMap::Entry* entry =
+            code_map->Lookup(event->code_start, hash, false);
+        CHECK(entry != NULL);
+        CHECK_EQ(reinterpret_cast<void*>(event->code_len),
+                 code_map->Remove(event->code_start, hash));
+        entry = code_map->Lookup(event->code_start, hash, true);
+        entry->value = reinterpret_cast<void*>(event->code_len);
+      }
+      break;
+
+    case v8::JitCodeEvent::CODE_REMOVED:
+      // Object/code removal events are currently not dispatched from the GC.
+      CHECK(false);
+      break;
+    default:
+      // Impossible event.
+      CHECK(false);
+      break;
+  }
+}
+
+
+// Implemented in the test-alloc.cc test suite.
+void SimulateFullSpace(i::PagedSpace* space);
+
+
+static bool MatchPointers(void* key1, void* key2) {
+  return key1 == key2;
+}
+
+
+TEST(SetJitCodeEventHandler) {
+  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; };"
+    "bar();";
+
+  // Run this test in a new isolate to make sure we don't
+  // have remnants of state from other code.
+  v8::Isolate* isolate = v8::Isolate::New();
+  isolate->Enter();
+
+  {
+    i::HashMap code(MatchPointers);
+    code_map = &code;
+    saw_bar = 0;
+    move_events = 0;
+
+    i::FLAG_stress_compaction = true;
+    V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, event_handler);
+
+    v8::HandleScope scope;
+    // Generate new code objects sparsely distributed across several
+    // different fragmented code-space pages.
+    const int kIterations = 10;
+    for (int i = 0; i < kIterations; ++i) {
+      LocalContext env;
+
+      v8::Handle<v8::Script> compiled_script;
+      {
+        i::AlwaysAllocateScope always_allocate;
+        SimulateFullSpace(HEAP->code_space());
+        compiled_script = v8_compile(script);
+      }
+      compiled_script->Run();
+
+      // Clear the compilation cache to get more wastage.
+      ISOLATE->compilation_cache()->Clear();
+    }
+
+    // Force code movement.
+    HEAP->CollectAllAvailableGarbage("TestSetJitCodeEventHandler");
+
+    CHECK_LE(kIterations, saw_bar);
+    CHECK_NE(0, move_events);
+
+    code_map = NULL;
+    V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
+  }
+
+  isolate->Exit();
+  isolate->Dispose();
+
+  // Do this in a new isolate.
+  isolate = v8::Isolate::New();
+  isolate->Enter();
+
+  // Verify that we get callbacks for existing code objects when we
+  // request enumeration of existing code.
+  {
+    v8::HandleScope scope;
+    LocalContext env;
+    CompileRun(script);
+
+    // Now get code through initial iteration.
+    i::HashMap code(MatchPointers);
+    code_map = &code;
+
+    V8::SetJitCodeEventHandler(v8::kJitCodeEventEnumExisting, event_handler);
+    V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
+
+    code_map = NULL;
+
+    // We expect that we got some events. Note that if we could get code removal
+    // notifications, we could compare two collections, one created by listening
+    // from the time of creation of an isolate, and the other by subscribing
+    // with EnumExisting.
+    CHECK_NE(0, code.occupancy());
+  }
+
+  isolate->Exit();
+  isolate->Dispose();
+}
+
+
 static int64_t cast(intptr_t x) { return static_cast<int64_t>(x); }