OLD | NEW |
(Empty) | |
| 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 #endif |
| 69 } |
| 70 |
| 71 private: |
| 72 v8::internal::Simulator* simulator_; |
| 73 }; |
| 74 #endif // USE_SIMULATOR |
| 75 |
| 76 |
| 77 class SamplingTestHelper { |
| 78 public: |
| 79 struct CodeEventEntry { |
| 80 std::string name; |
| 81 const void* code_start; |
| 82 size_t code_len; |
| 83 }; |
| 84 typedef std::map<const void*, CodeEventEntry> CodeEntries; |
| 85 |
| 86 explicit SamplingTestHelper(const std::string& test_function) |
| 87 : sample_is_taken_(false), isolate_(CcTest::isolate()) { |
| 88 DCHECK_EQ(NULL, instance_); |
| 89 instance_ = this; |
| 90 v8::HandleScope scope(isolate_); |
| 91 v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_); |
| 92 global->Set(v8::String::NewFromUtf8(isolate_, "CollectSample"), |
| 93 v8::FunctionTemplate::New(isolate_, CollectSample)); |
| 94 LocalContext env(isolate_, NULL, global); |
| 95 isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, |
| 96 JitCodeEventHandler); |
| 97 v8::Script::Compile( |
| 98 v8::String::NewFromUtf8(isolate_, test_function.c_str()))->Run(); |
| 99 } |
| 100 |
| 101 ~SamplingTestHelper() { |
| 102 isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL); |
| 103 instance_ = NULL; |
| 104 } |
| 105 |
| 106 Sample& sample() { return sample_; } |
| 107 |
| 108 const CodeEventEntry* FindEventEntry(const void* address) { |
| 109 CodeEntries::const_iterator it = code_entries_.upper_bound(address); |
| 110 if (it == code_entries_.begin()) return NULL; |
| 111 const CodeEventEntry& entry = (--it)->second; |
| 112 const void* code_end = |
| 113 static_cast<const uint8_t*>(entry.code_start) + entry.code_len; |
| 114 return address < code_end ? &entry : NULL; |
| 115 } |
| 116 |
| 117 private: |
| 118 static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| 119 instance_->DoCollectSample(); |
| 120 } |
| 121 |
| 122 static void JitCodeEventHandler(const v8::JitCodeEvent* event) { |
| 123 instance_->DoJitCodeEventHandler(event); |
| 124 } |
| 125 |
| 126 // The JavaScript calls this function when on full stack depth. |
| 127 void DoCollectSample() { |
| 128 v8::RegisterState state; |
| 129 #if defined(USE_SIMULATOR) |
| 130 SimulatorHelper simulator_helper; |
| 131 if (!simulator_helper.Init(isolate_)) return; |
| 132 simulator_helper.FillRegisters(&state); |
| 133 #else |
| 134 state.pc = NULL; |
| 135 state.fp = &state; |
| 136 state.sp = &state; |
| 137 #endif |
| 138 v8::SampleInfo info; |
| 139 isolate_->GetStackSample(state, sample_.data().start(), |
| 140 static_cast<size_t>(sample_.size()), &info); |
| 141 size_t frames_count = info.frames_count; |
| 142 CHECK_LE(frames_count, static_cast<size_t>(sample_.size())); |
| 143 sample_.data().Truncate(static_cast<int>(frames_count)); |
| 144 sample_is_taken_ = true; |
| 145 } |
| 146 |
| 147 void DoJitCodeEventHandler(const v8::JitCodeEvent* event) { |
| 148 if (sample_is_taken_) return; |
| 149 switch (event->type) { |
| 150 case v8::JitCodeEvent::CODE_ADDED: { |
| 151 CodeEventEntry entry; |
| 152 entry.name = std::string(event->name.str, event->name.len); |
| 153 entry.code_start = event->code_start; |
| 154 entry.code_len = event->code_len; |
| 155 code_entries_.insert(std::make_pair(entry.code_start, entry)); |
| 156 break; |
| 157 } |
| 158 case v8::JitCodeEvent::CODE_MOVED: { |
| 159 CodeEntries::iterator it = code_entries_.find(event->code_start); |
| 160 CHECK(it != code_entries_.end()); |
| 161 code_entries_.erase(it); |
| 162 CodeEventEntry entry; |
| 163 entry.name = std::string(event->name.str, event->name.len); |
| 164 entry.code_start = event->new_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_REMOVED: |
| 170 code_entries_.erase(event->code_start); |
| 171 break; |
| 172 default: |
| 173 break; |
| 174 } |
| 175 } |
| 176 |
| 177 Sample sample_; |
| 178 bool sample_is_taken_; |
| 179 v8::Isolate* isolate_; |
| 180 CodeEntries code_entries_; |
| 181 |
| 182 static SamplingTestHelper* instance_; |
| 183 }; |
| 184 |
| 185 SamplingTestHelper* SamplingTestHelper::instance_; |
| 186 |
| 187 } // namespace |
| 188 |
| 189 |
| 190 // A JavaScript function which takes stack depth |
| 191 // (minimum value 2) as an argument. |
| 192 // When at the bottom of the recursion, |
| 193 // the JavaScript code calls into C++ test code, |
| 194 // waiting for the sampler to take a sample. |
| 195 static const char* test_function = |
| 196 "function func(depth) {" |
| 197 " if (depth == 2) CollectSample();" |
| 198 " else return func(depth - 1);" |
| 199 "}"; |
| 200 |
| 201 |
| 202 TEST(StackDepthIsConsistent) { |
| 203 SamplingTestHelper helper(std::string(test_function) + "func(8);"); |
| 204 CHECK_EQ(8, helper.sample().size()); |
| 205 } |
| 206 |
| 207 |
| 208 TEST(StackDepthDoesNotExceedMaxValue) { |
| 209 SamplingTestHelper helper(std::string(test_function) + "func(300);"); |
| 210 CHECK_EQ(Sample::kFramesLimit, helper.sample().size()); |
| 211 } |
| 212 |
| 213 |
| 214 // The captured sample should have three pc values. |
| 215 // They should fall in the range where the compiled code resides. |
| 216 // The expected stack is: |
| 217 // bottom of stack [{anon script}, outer, inner] top of stack |
| 218 // ^ ^ ^ |
| 219 // sample.stack indices 2 1 0 |
| 220 TEST(StackFramesConsistent) { |
| 221 // Note: The arguments.callee stuff is there so that the |
| 222 // functions are not optimized away. |
| 223 const char* test_script = |
| 224 "function test_sampler_api_inner() {" |
| 225 " CollectSample();" |
| 226 " return arguments.callee.toString();" |
| 227 "}" |
| 228 "function test_sampler_api_outer() {" |
| 229 " return test_sampler_api_inner() + arguments.callee.toString();" |
| 230 "}" |
| 231 "test_sampler_api_outer();"; |
| 232 |
| 233 SamplingTestHelper helper(test_script); |
| 234 Sample& sample = helper.sample(); |
| 235 CHECK_EQ(3, sample.size()); |
| 236 |
| 237 const SamplingTestHelper::CodeEventEntry* entry; |
| 238 entry = helper.FindEventEntry(sample.begin()[0]); |
| 239 CHECK_NE(NULL, entry); |
| 240 CHECK(std::string::npos != entry->name.find("test_sampler_api_inner")); |
| 241 |
| 242 entry = helper.FindEventEntry(sample.begin()[1]); |
| 243 CHECK_NE(NULL, entry); |
| 244 CHECK(std::string::npos != entry->name.find("test_sampler_api_outer")); |
| 245 } |
OLD | NEW |