Home | History | Annotate | Download | only in cctest
      1 // Copyright 2014 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 //
      5 // Tests the sampling API in include/v8.h
      6 
      7 #include <map>
      8 #include <string>
      9 #include "include/v8.h"
     10 #include "src/simulator.h"
     11 #include "test/cctest/cctest.h"
     12 
     13 namespace {
     14 
     15 class Sample {
     16  public:
     17   enum { kFramesLimit = 255 };
     18 
     19   Sample() {}
     20 
     21   typedef const void* const* const_iterator;
     22   const_iterator begin() const { return data_.start(); }
     23   const_iterator end() const { return &data_[data_.length()]; }
     24 
     25   int size() const { return data_.length(); }
     26   v8::internal::Vector<void*>& data() { return data_; }
     27 
     28  private:
     29   v8::internal::EmbeddedVector<void*, kFramesLimit> data_;
     30 };
     31 
     32 
     33 #if defined(USE_SIMULATOR)
     34 class SimulatorHelper {
     35  public:
     36   inline bool Init(v8::Isolate* isolate) {
     37     simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
     38                      ->thread_local_top()
     39                      ->simulator_;
     40     // Check if there is active simulator.
     41     return simulator_ != NULL;
     42   }
     43 
     44   inline void FillRegisters(v8::RegisterState* state) {
     45 #if V8_TARGET_ARCH_ARM
     46     state->pc = reinterpret_cast<void*>(simulator_->get_pc());
     47     state->sp = reinterpret_cast<void*>(
     48         simulator_->get_register(v8::internal::Simulator::sp));
     49     state->fp = reinterpret_cast<void*>(
     50         simulator_->get_register(v8::internal::Simulator::r11));
     51 #elif V8_TARGET_ARCH_ARM64
     52     if (simulator_->sp() == 0 || simulator_->fp() == 0) {
     53       // It's possible that the simulator is interrupted while it is updating
     54       // the sp or fp register. ARM64 simulator does this in two steps:
     55       // first setting it to zero and then setting it to a new value.
     56       // Bailout if sp/fp doesn't contain the new value.
     57       return;
     58     }
     59     state->pc = reinterpret_cast<void*>(simulator_->pc());
     60     state->sp = reinterpret_cast<void*>(simulator_->sp());
     61     state->fp = reinterpret_cast<void*>(simulator_->fp());
     62 #elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
     63     state->pc = reinterpret_cast<void*>(simulator_->get_pc());
     64     state->sp = reinterpret_cast<void*>(
     65         simulator_->get_register(v8::internal::Simulator::sp));
     66     state->fp = reinterpret_cast<void*>(
     67         simulator_->get_register(v8::internal::Simulator::fp));
     68 #elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
     69     state->pc = reinterpret_cast<void*>(simulator_->get_pc());
     70     state->sp = reinterpret_cast<void*>(
     71         simulator_->get_register(v8::internal::Simulator::sp));
     72     state->fp = reinterpret_cast<void*>(
     73         simulator_->get_register(v8::internal::Simulator::fp));
     74 #elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
     75     state->pc = reinterpret_cast<void*>(simulator_->get_pc());
     76     state->sp = reinterpret_cast<void*>(
     77         simulator_->get_register(v8::internal::Simulator::sp));
     78     state->fp = reinterpret_cast<void*>(
     79         simulator_->get_register(v8::internal::Simulator::fp));
     80 #endif
     81   }
     82 
     83  private:
     84   v8::internal::Simulator* simulator_;
     85 };
     86 #endif  // USE_SIMULATOR
     87 
     88 
     89 class SamplingTestHelper {
     90  public:
     91   struct CodeEventEntry {
     92     std::string name;
     93     const void* code_start;
     94     size_t code_len;
     95   };
     96   typedef std::map<const void*, CodeEventEntry> CodeEntries;
     97 
     98   explicit SamplingTestHelper(const std::string& test_function)
     99       : sample_is_taken_(false), isolate_(CcTest::isolate()) {
    100     CHECK(!instance_);
    101     instance_ = this;
    102     v8::HandleScope scope(isolate_);
    103     v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
    104     global->Set(v8_str("CollectSample"),
    105                 v8::FunctionTemplate::New(isolate_, CollectSample));
    106     LocalContext env(isolate_, NULL, global);
    107     isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
    108                                      JitCodeEventHandler);
    109     CompileRun(v8_str(test_function.c_str()));
    110   }
    111 
    112   ~SamplingTestHelper() {
    113     isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
    114     instance_ = NULL;
    115   }
    116 
    117   Sample& sample() { return sample_; }
    118 
    119   const CodeEventEntry* FindEventEntry(const void* address) {
    120     CodeEntries::const_iterator it = code_entries_.upper_bound(address);
    121     if (it == code_entries_.begin()) return NULL;
    122     const CodeEventEntry& entry = (--it)->second;
    123     const void* code_end =
    124         static_cast<const uint8_t*>(entry.code_start) + entry.code_len;
    125     return address < code_end ? &entry : NULL;
    126   }
    127 
    128  private:
    129   static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) {
    130     instance_->DoCollectSample();
    131   }
    132 
    133   static void JitCodeEventHandler(const v8::JitCodeEvent* event) {
    134     instance_->DoJitCodeEventHandler(event);
    135   }
    136 
    137   // The JavaScript calls this function when on full stack depth.
    138   void DoCollectSample() {
    139     v8::RegisterState state;
    140 #if defined(USE_SIMULATOR)
    141     SimulatorHelper simulator_helper;
    142     if (!simulator_helper.Init(isolate_)) return;
    143     simulator_helper.FillRegisters(&state);
    144 #else
    145     state.pc = NULL;
    146     state.fp = &state;
    147     state.sp = &state;
    148 #endif
    149     v8::SampleInfo info;
    150     isolate_->GetStackSample(state, sample_.data().start(),
    151                              static_cast<size_t>(sample_.size()), &info);
    152     size_t frames_count = info.frames_count;
    153     CHECK_LE(frames_count, static_cast<size_t>(sample_.size()));
    154     sample_.data().Truncate(static_cast<int>(frames_count));
    155     sample_is_taken_ = true;
    156   }
    157 
    158   void DoJitCodeEventHandler(const v8::JitCodeEvent* event) {
    159     if (sample_is_taken_) return;
    160     switch (event->type) {
    161       case v8::JitCodeEvent::CODE_ADDED: {
    162         CodeEventEntry entry;
    163         entry.name = std::string(event->name.str, event->name.len);
    164         entry.code_start = event->code_start;
    165         entry.code_len = event->code_len;
    166         code_entries_.insert(std::make_pair(entry.code_start, entry));
    167         break;
    168       }
    169       case v8::JitCodeEvent::CODE_MOVED: {
    170         CodeEntries::iterator it = code_entries_.find(event->code_start);
    171         CHECK(it != code_entries_.end());
    172         code_entries_.erase(it);
    173         CodeEventEntry entry;
    174         entry.name = std::string(event->name.str, event->name.len);
    175         entry.code_start = event->new_code_start;
    176         entry.code_len = event->code_len;
    177         code_entries_.insert(std::make_pair(entry.code_start, entry));
    178         break;
    179       }
    180       case v8::JitCodeEvent::CODE_REMOVED:
    181         code_entries_.erase(event->code_start);
    182         break;
    183       default:
    184         break;
    185     }
    186   }
    187 
    188   Sample sample_;
    189   bool sample_is_taken_;
    190   v8::Isolate* isolate_;
    191   CodeEntries code_entries_;
    192 
    193   static SamplingTestHelper* instance_;
    194 };
    195 
    196 SamplingTestHelper* SamplingTestHelper::instance_;
    197 
    198 }  // namespace
    199 
    200 
    201 // A JavaScript function which takes stack depth
    202 // (minimum value 2) as an argument.
    203 // When at the bottom of the recursion,
    204 // the JavaScript code calls into C++ test code,
    205 // waiting for the sampler to take a sample.
    206 static const char* test_function =
    207     "function func(depth) {"
    208     "  if (depth == 2) CollectSample();"
    209     "  else return func(depth - 1);"
    210     "}";
    211 
    212 
    213 TEST(StackDepthIsConsistent) {
    214   SamplingTestHelper helper(std::string(test_function) + "func(8);");
    215   CHECK_EQ(8, helper.sample().size());
    216 }
    217 
    218 
    219 TEST(StackDepthDoesNotExceedMaxValue) {
    220   SamplingTestHelper helper(std::string(test_function) + "func(300);");
    221   CHECK_EQ(Sample::kFramesLimit, helper.sample().size());
    222 }
    223 
    224 
    225 // The captured sample should have three pc values.
    226 // They should fall in the range where the compiled code resides.
    227 // The expected stack is:
    228 // bottom of stack [{anon script}, outer, inner] top of stack
    229 //                              ^      ^       ^
    230 // sample.stack indices         2      1       0
    231 TEST(StackFramesConsistent) {
    232   i::FLAG_allow_natives_syntax = true;
    233   const char* test_script =
    234       "function test_sampler_api_inner() {"
    235       "  CollectSample();"
    236       "  return 0;"
    237       "}"
    238       "function test_sampler_api_outer() {"
    239       "  return test_sampler_api_inner();"
    240       "}"
    241       "%NeverOptimizeFunction(test_sampler_api_inner);"
    242       "%NeverOptimizeFunction(test_sampler_api_outer);"
    243       "test_sampler_api_outer();";
    244 
    245   SamplingTestHelper helper(test_script);
    246   Sample& sample = helper.sample();
    247   CHECK_EQ(3, sample.size());
    248 
    249   const SamplingTestHelper::CodeEventEntry* entry;
    250   entry = helper.FindEventEntry(sample.begin()[0]);
    251   CHECK(entry);
    252   CHECK(std::string::npos != entry->name.find("test_sampler_api_inner"));
    253 
    254   entry = helper.FindEventEntry(sample.begin()[1]);
    255   CHECK(entry);
    256   CHECK(std::string::npos != entry->name.find("test_sampler_api_outer"));
    257 }
    258