// Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // Tests the sampling API in include/v8.h #include #include #include "include/v8.h" #include "src/execution/simulator.h" #include "src/flags/flags.h" #include "test/cctest/cctest.h" namespace { class Sample { public: enum { kFramesLimit = 255 }; Sample() = default; using const_iterator = const void* const*; const_iterator begin() const { return data_.begin(); } const_iterator end() const { return &data_[data_.length()]; } int size() const { return data_.length(); } v8::internal::Vector& data() { return data_; } private: v8::internal::EmbeddedVector data_; }; #if defined(USE_SIMULATOR) class SimulatorHelper { public: inline bool Init(v8::Isolate* isolate) { simulator_ = reinterpret_cast(isolate) ->thread_local_top() ->simulator_; // Check if there is active simulator. return simulator_ != nullptr; } inline void FillRegisters(v8::RegisterState* state) { #if V8_TARGET_ARCH_ARM state->pc = reinterpret_cast(simulator_->get_pc()); state->sp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::sp)); state->fp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::r11)); state->lr = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::lr)); #elif V8_TARGET_ARCH_ARM64 if (simulator_->sp() == 0 || simulator_->fp() == 0) { // It's possible that the simulator is interrupted while it is updating // the sp or fp register. ARM64 simulator does this in two steps: // first setting it to zero and then setting it to a new value. // Bailout if sp/fp doesn't contain the new value. return; } state->pc = reinterpret_cast(simulator_->pc()); state->sp = reinterpret_cast(simulator_->sp()); state->fp = reinterpret_cast(simulator_->fp()); state->lr = reinterpret_cast(simulator_->lr()); #elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 state->pc = reinterpret_cast(simulator_->get_pc()); state->sp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::sp)); state->fp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::fp)); #elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64 state->pc = reinterpret_cast(simulator_->get_pc()); state->sp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::sp)); state->fp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::fp)); state->lr = reinterpret_cast(simulator_->get_lr()); #elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X state->pc = reinterpret_cast(simulator_->get_pc()); state->sp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::sp)); state->fp = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::fp)); state->lr = reinterpret_cast( simulator_->get_register(v8::internal::Simulator::ra)); #endif } private: v8::internal::Simulator* simulator_; }; #endif // USE_SIMULATOR class SamplingTestHelper { public: struct CodeEventEntry { std::string name; const void* code_start; size_t code_len; }; using CodeEntries = std::map; explicit SamplingTestHelper(const std::string& test_function) : sample_is_taken_(false), isolate_(CcTest::isolate()) { CHECK(!instance_); instance_ = this; v8::HandleScope scope(isolate_); v8::Local global = v8::ObjectTemplate::New(isolate_); global->Set(v8_str("CollectSample"), v8::FunctionTemplate::New(isolate_, CollectSample)); LocalContext env(isolate_, nullptr, global); isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, JitCodeEventHandler); CompileRun(v8_str(test_function.c_str())); } ~SamplingTestHelper() { isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, nullptr); instance_ = nullptr; } Sample& sample() { return sample_; } const CodeEventEntry* FindEventEntry(const void* address) { CodeEntries::const_iterator it = code_entries_.upper_bound(address); if (it == code_entries_.begin()) return nullptr; const CodeEventEntry& entry = (--it)->second; const void* code_end = static_cast(entry.code_start) + entry.code_len; return address < code_end ? &entry : nullptr; } private: static void CollectSample(const v8::FunctionCallbackInfo& args) { instance_->DoCollectSample(); } static void JitCodeEventHandler(const v8::JitCodeEvent* event) { instance_->DoJitCodeEventHandler(event); } // The JavaScript calls this function when on full stack depth. void DoCollectSample() { v8::RegisterState state; #if defined(USE_SIMULATOR) SimulatorHelper simulator_helper; if (!simulator_helper.Init(isolate_)) return; simulator_helper.FillRegisters(&state); #else state.pc = nullptr; state.fp = &state; state.sp = &state; #endif v8::SampleInfo info; isolate_->GetStackSample(state, sample_.data().begin(), static_cast(sample_.size()), &info); size_t frames_count = info.frames_count; CHECK_LE(frames_count, static_cast(sample_.size())); sample_.data().Truncate(static_cast(frames_count)); sample_is_taken_ = true; } void DoJitCodeEventHandler(const v8::JitCodeEvent* event) { if (sample_is_taken_) return; switch (event->type) { case v8::JitCodeEvent::CODE_ADDED: { CodeEventEntry entry; entry.name = std::string(event->name.str, event->name.len); entry.code_start = event->code_start; entry.code_len = event->code_len; code_entries_.insert(std::make_pair(entry.code_start, entry)); break; } case v8::JitCodeEvent::CODE_MOVED: { CodeEntries::iterator it = code_entries_.find(event->code_start); CHECK(it != code_entries_.end()); code_entries_.erase(it); CodeEventEntry entry; entry.name = std::string(event->name.str, event->name.len); entry.code_start = event->new_code_start; entry.code_len = event->code_len; code_entries_.insert(std::make_pair(entry.code_start, entry)); break; } case v8::JitCodeEvent::CODE_REMOVED: code_entries_.erase(event->code_start); break; default: break; } } Sample sample_; bool sample_is_taken_; v8::Isolate* isolate_; CodeEntries code_entries_; static SamplingTestHelper* instance_; }; SamplingTestHelper* SamplingTestHelper::instance_; } // namespace // A JavaScript function which takes stack depth // (minimum value 2) as an argument. // When at the bottom of the recursion, // the JavaScript code calls into C++ test code, // waiting for the sampler to take a sample. static const char* test_function = "function func(depth) {" " if (depth == 2) CollectSample();" " else return func(depth - 1);" "}"; TEST(StackDepthIsConsistent) { SamplingTestHelper helper(std::string(test_function) + "func(8);"); CHECK_EQ(8, helper.sample().size()); } TEST(StackDepthDoesNotExceedMaxValue) { SamplingTestHelper helper(std::string(test_function) + "func(300);"); CHECK_EQ(Sample::kFramesLimit, helper.sample().size()); } // The captured sample should have three pc values. // They should fall in the range where the compiled code resides. // The expected stack is: // bottom of stack [{anon script}, outer, inner] top of stack // ^ ^ ^ // sample.stack indices 2 1 0 TEST(StackFramesConsistent) { i::FLAG_allow_natives_syntax = true; const char* test_script = "function test_sampler_api_inner() {" " CollectSample();" " return 0;" "}" "function test_sampler_api_outer() {" " return test_sampler_api_inner();" "}" "%NeverOptimizeFunction(test_sampler_api_inner);" "%NeverOptimizeFunction(test_sampler_api_outer);" "test_sampler_api_outer();"; SamplingTestHelper helper(test_script); Sample& sample = helper.sample(); CHECK_EQ(3, sample.size()); const SamplingTestHelper::CodeEventEntry* entry; entry = helper.FindEventEntry(sample.begin()[0]); CHECK(entry); CHECK(std::string::npos != entry->name.find("test_sampler_api_inner")); entry = helper.FindEventEntry(sample.begin()[1]); CHECK(entry); CHECK(std::string::npos != entry->name.find("test_sampler_api_outer")); }