Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 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)); |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 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)); |
Ben Murdoch | da12d29 | 2016-06-02 14:46:10 +0100 | [diff] [blame] | 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)); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 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()) { |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 100 | CHECK(!instance_); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 101 | instance_ = this; |
| 102 | v8::HandleScope scope(isolate_); |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 103 | v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_); |
| 104 | global->Set(v8_str("CollectSample"), |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 105 | v8::FunctionTemplate::New(isolate_, CollectSample)); |
| 106 | LocalContext env(isolate_, NULL, global); |
| 107 | isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, |
| 108 | JitCodeEventHandler); |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 109 | CompileRun(v8_str(test_function.c_str())); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 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) { |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 232 | i::FLAG_allow_natives_syntax = true; |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 233 | const char* test_script = |
| 234 | "function test_sampler_api_inner() {" |
| 235 | " CollectSample();" |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 236 | " return 0;" |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 237 | "}" |
| 238 | "function test_sampler_api_outer() {" |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 239 | " return test_sampler_api_inner();" |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 240 | "}" |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 241 | "%NeverOptimizeFunction(test_sampler_api_inner);" |
| 242 | "%NeverOptimizeFunction(test_sampler_api_outer);" |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 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]); |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 251 | CHECK(entry); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 252 | CHECK(std::string::npos != entry->name.find("test_sampler_api_inner")); |
| 253 | |
| 254 | entry = helper.FindEventEntry(sample.begin()[1]); |
Ben Murdoch | 4a90d5f | 2016-03-22 12:00:34 +0000 | [diff] [blame] | 255 | CHECK(entry); |
Emily Bernier | d0a1eb7 | 2015-03-24 16:35:39 -0400 | [diff] [blame] | 256 | CHECK(std::string::npos != entry->name.find("test_sampler_api_outer")); |
| 257 | } |