2014-09-29 12:59:54 +00:00
|
|
|
// 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 <map>
|
|
|
|
#include <string>
|
|
|
|
#include "include/v8.h"
|
2016-09-01 12:01:33 +00:00
|
|
|
#include "src/flags.h"
|
2014-09-29 12:59:54 +00:00
|
|
|
#include "src/simulator.h"
|
|
|
|
#include "test/cctest/cctest.h"
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
class Sample {
|
|
|
|
public:
|
|
|
|
enum { kFramesLimit = 255 };
|
|
|
|
|
2018-09-13 10:07:40 +00:00
|
|
|
Sample() = default;
|
2014-09-29 12:59:54 +00:00
|
|
|
|
|
|
|
typedef const void* const* const_iterator;
|
|
|
|
const_iterator begin() const { return data_.start(); }
|
|
|
|
const_iterator end() const { return &data_[data_.length()]; }
|
|
|
|
|
|
|
|
int size() const { return data_.length(); }
|
|
|
|
v8::internal::Vector<void*>& data() { return data_; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
v8::internal::EmbeddedVector<void*, kFramesLimit> data_;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(USE_SIMULATOR)
|
|
|
|
class SimulatorHelper {
|
|
|
|
public:
|
|
|
|
inline bool Init(v8::Isolate* isolate) {
|
|
|
|
simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
|
|
|
|
->thread_local_top()
|
|
|
|
->simulator_;
|
|
|
|
// Check if there is active simulator.
|
2017-10-13 16:33:03 +00:00
|
|
|
return simulator_ != nullptr;
|
2014-09-29 12:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inline void FillRegisters(v8::RegisterState* state) {
|
|
|
|
#if V8_TARGET_ARCH_ARM
|
|
|
|
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
|
|
|
state->sp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::sp));
|
|
|
|
state->fp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::r11));
|
|
|
|
#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<void*>(simulator_->pc());
|
|
|
|
state->sp = reinterpret_cast<void*>(simulator_->sp());
|
|
|
|
state->fp = reinterpret_cast<void*>(simulator_->fp());
|
|
|
|
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
|
|
|
|
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
|
|
|
state->sp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::sp));
|
|
|
|
state->fp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::fp));
|
2015-01-30 08:01:53 +00:00
|
|
|
#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
|
|
|
|
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
|
|
|
state->sp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::sp));
|
|
|
|
state->fp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::fp));
|
2016-03-15 18:07:43 +00:00
|
|
|
#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
|
|
|
|
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
|
|
|
state->sp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::sp));
|
|
|
|
state->fp = reinterpret_cast<void*>(
|
|
|
|
simulator_->get_register(v8::internal::Simulator::fp));
|
2014-09-29 12:59:54 +00:00
|
|
|
#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;
|
|
|
|
};
|
|
|
|
typedef std::map<const void*, CodeEventEntry> CodeEntries;
|
|
|
|
|
|
|
|
explicit SamplingTestHelper(const std::string& test_function)
|
|
|
|
: sample_is_taken_(false), isolate_(CcTest::isolate()) {
|
2015-12-07 05:36:41 +00:00
|
|
|
CHECK(!instance_);
|
2014-09-29 12:59:54 +00:00
|
|
|
instance_ = this;
|
|
|
|
v8::HandleScope scope(isolate_);
|
2015-10-08 09:48:05 +00:00
|
|
|
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
|
|
|
|
global->Set(v8_str("CollectSample"),
|
2014-09-29 12:59:54 +00:00
|
|
|
v8::FunctionTemplate::New(isolate_, CollectSample));
|
2017-10-13 16:33:03 +00:00
|
|
|
LocalContext env(isolate_, nullptr, global);
|
2014-09-29 12:59:54 +00:00
|
|
|
isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
|
|
|
|
JitCodeEventHandler);
|
2015-10-08 09:48:05 +00:00
|
|
|
CompileRun(v8_str(test_function.c_str()));
|
2014-09-29 12:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
~SamplingTestHelper() {
|
2017-10-13 16:33:03 +00:00
|
|
|
isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, nullptr);
|
|
|
|
instance_ = nullptr;
|
2014-09-29 12:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Sample& sample() { return sample_; }
|
|
|
|
|
|
|
|
const CodeEventEntry* FindEventEntry(const void* address) {
|
|
|
|
CodeEntries::const_iterator it = code_entries_.upper_bound(address);
|
2017-10-13 16:33:03 +00:00
|
|
|
if (it == code_entries_.begin()) return nullptr;
|
2014-09-29 12:59:54 +00:00
|
|
|
const CodeEventEntry& entry = (--it)->second;
|
|
|
|
const void* code_end =
|
|
|
|
static_cast<const uint8_t*>(entry.code_start) + entry.code_len;
|
2017-10-13 16:33:03 +00:00
|
|
|
return address < code_end ? &entry : nullptr;
|
2014-09-29 12:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& 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
|
2017-10-13 16:33:03 +00:00
|
|
|
state.pc = nullptr;
|
2014-09-29 12:59:54 +00:00
|
|
|
state.fp = &state;
|
|
|
|
state.sp = &state;
|
|
|
|
#endif
|
|
|
|
v8::SampleInfo info;
|
|
|
|
isolate_->GetStackSample(state, sample_.data().start(),
|
|
|
|
static_cast<size_t>(sample_.size()), &info);
|
|
|
|
size_t frames_count = info.frames_count;
|
|
|
|
CHECK_LE(frames_count, static_cast<size_t>(sample_.size()));
|
|
|
|
sample_.data().Truncate(static_cast<int>(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) {
|
2015-10-15 12:01:52 +00:00
|
|
|
i::FLAG_allow_natives_syntax = true;
|
2014-09-29 12:59:54 +00:00
|
|
|
const char* test_script =
|
|
|
|
"function test_sampler_api_inner() {"
|
|
|
|
" CollectSample();"
|
2015-10-15 12:01:52 +00:00
|
|
|
" return 0;"
|
2014-09-29 12:59:54 +00:00
|
|
|
"}"
|
|
|
|
"function test_sampler_api_outer() {"
|
2015-10-15 12:01:52 +00:00
|
|
|
" return test_sampler_api_inner();"
|
2014-09-29 12:59:54 +00:00
|
|
|
"}"
|
2015-10-15 12:01:52 +00:00
|
|
|
"%NeverOptimizeFunction(test_sampler_api_inner);"
|
|
|
|
"%NeverOptimizeFunction(test_sampler_api_outer);"
|
2014-09-29 12:59:54 +00:00
|
|
|
"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]);
|
2015-01-30 09:29:25 +00:00
|
|
|
CHECK(entry);
|
2014-09-29 12:59:54 +00:00
|
|
|
CHECK(std::string::npos != entry->name.find("test_sampler_api_inner"));
|
|
|
|
|
|
|
|
entry = helper.FindEventEntry(sample.begin()[1]);
|
2015-01-30 09:29:25 +00:00
|
|
|
CHECK(entry);
|
2014-09-29 12:59:54 +00:00
|
|
|
CHECK(std::string::npos != entry->name.find("test_sampler_api_outer"));
|
|
|
|
}
|