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