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